better-auth-integration
Guide only for applications using @dodopayments/better-auth, covering authenticated customer sync, checkout, portal access, usage ingestion, and verified webhook callbacks.
Works with
---
name: better-auth-integration
description: Guide only for applications using @dodopayments/better-auth, covering authenticated customer sync, checkout, portal access, usage ingestion, and verified webhook callbacks.
license: MIT
---
# Better Auth Integration
Use `@dodopayments/better-auth` to synchronize Better Auth users with Dodo Payments and expose authenticated checkout, customer portal, subscription, payment, usage, and webhook endpoints.
## When to use this skill
- Create a Dodo customer automatically when a Better Auth user signs up.
- Start checkout for a signed-in user without exposing a Dodo API key.
- Let users open their customer portal or list their subscriptions and payments.
- Submit metered usage for the signed-in customer.
- Receive signature-verified Dodo webhooks through Better Auth.
## Install
```bash
npm install @dodopayments/better-auth dodopayments better-auth
```
The package exports the lowercase server functions `dodopayments`, `checkout`, `portal`, `usage`, and `webhooks`. Its client entry point exports `dodopaymentsClient`.
## Server setup
Every endpoint is registered by a feature in `use`. Include every feature that the application calls; omitting one makes its route return 404.
```typescript
import { betterAuth } from "better-auth";
import DodoPayments from "dodopayments";
import { checkout, dodopayments, portal, usage, webhooks } from "@dodopayments/better-auth";
const dodoPayments = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: "test_mode",
});
export const auth = betterAuth({
database: {
// your database config
},
plugins: [
dodopayments({
client: dodoPayments,
createCustomerOnSignUp: true,
// `user` here is Better Auth's base user: id, name, email, emailVerified,
// image, createdAt, updatedAt. Anything else (phone number, company, plan)
// must first be declared via `user.additionalFields` in your Better Auth
// config, otherwise it does not exist on the type and will be undefined.
getCustomerParams: (user) => ({
metadata: { better_auth_user_id: user.id },
}),
use: [
checkout({
products: [
{ productId: "pdt_premium", slug: "premium-plan" },
],
successUrl: "https://app.example.com/billing/success",
authenticatedUsersOnly: true,
}),
portal(),
usage(),
webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
onPayload: async (payload) => {
console.log("Verified Dodo webhook:", payload.type);
},
}),
],
}),
],
});
```
`products` maps an application slug to a Dodo product ID. `authenticatedUsersOnly: true` requires a Better Auth session for checkout. `getCustomerParams` adds application metadata when customer synchronization runs.
## Browser client setup
```typescript
import { createAuthClient } from "better-auth/react";
import { dodopaymentsClient } from "@dodopayments/better-auth/client";
export const authClient = createAuthClient({
plugins: [dodopaymentsClient()],
});
```
These are browser methods. They send the Better Auth session cookie automatically; do not pass `request.headers` to them.
## Create a checkout session
```typescript
import { authClient } from "@/lib/auth-client";
async function startCheckout() {
const { data: session, error } = await authClient.dodopayments.checkoutSession({
slug: "premium-plan",
referenceId: "order_123",
});
if (error) {
console.error("Checkout error:", error);
return;
}
if (session) {
window.location.href = session.url;
}
}
```
`slug` must match a product configured in `checkout({ products: [...] })`. `referenceId` is optional reconciliation metadata.
## Open the customer portal
```typescript
const { data: portalSession, error } =
await authClient.dodopayments.customer.portal();
if (error) {
console.error("Portal error:", error);
} else if (portalSession?.redirect) {
window.location.href = portalSession.url;
}
```
The portal endpoint requires a signed-in, verified user and resolves that user's synchronized Dodo customer.
## List subscriptions and payments
```typescript
const { data: subscriptions, error: subscriptionsError } =
await authClient.dodopayments.customer.subscriptions.list({
query: { page: 1, limit: 10, status: "active" },
});
const { data: payments, error: paymentsError } =
await authClient.dodopayments.customer.payments.list({
query: { page: 1, limit: 10, status: "succeeded" },
});
if (subscriptionsError || paymentsError) {
console.error("Could not load billing history");
} else {
console.log("Subscriptions:", subscriptions?.items);
console.log("Payments:", payments?.items);
}
```
Both list responses use an `items` envelope. Subscription filters accept `page`, `limit`, and `status`; payment filters use the same pagination fields and payment statuses.
## Ingest metered usage
Enable `usage()` on the server, then submit an event for the signed-in customer:
```typescript
const { data, error } = await authClient.dodopayments.usage.ingest({
event_id: "evt_usage_123",
event_name: "api_calls",
metadata: { requests: 100 },
timestamp: new Date(),
});
if (error) {
console.error("Usage ingestion failed:", error);
} else {
console.log("Ingested events:", data?.ingested_count);
}
```
The plugin derives `customer_id` from the authenticated session. `event_id` is the event's idempotency key; reuse it when retrying the same event.
## Webhook endpoint
`webhooks({ webhookKey, onPayload })` verifies and handles the request itself. With Better Auth's default `/api/auth` base path, configure this URL in the Dodo dashboard:
```text
https://app.example.com/api/auth/dodopayments/webhooks
```
Do not create a second route and do not call `auth.api.dodopayments.verifyWebhook()`; that method does not exist. The plugin reads the raw request, verifies the Standard Webhooks signature with `webhookKey`, and invokes `onPayload` plus any configured event-specific callback.
## Registered endpoints
- `POST /dodopayments/checkout`
- `POST /dodopayments/checkout-session`
- `GET /dodopayments/customer/portal`
- `GET /dodopayments/customer/subscriptions/list`
- `GET /dodopayments/customer/payments/list`
- `POST /dodopayments/usage/ingest`
- `GET /dodopayments/usage/meters/list`
- `POST /dodopayments/webhooks`
Better Auth prefixes these plugin paths with its configured API base path.
## Common mistakes
**Enabling only `portal()`.** Checkout, usage, and webhook routes do not exist unless `checkout()`, `usage()`, and `webhooks()` are also present in `use`.
**Mixing browser and server APIs.** Call `authClient.dodopayments...` in browser code without a `headers` option. Use Better Auth's server API separately when handling server requests.
**Creating duplicate customers.** When `createCustomerOnSignUp` is enabled, do not also call `client.customers.create()` for the same user.
**Granting access from browser state.** Treat verified webhook events or a server-side Dodo lookup as the source of truth for entitlements.
**Adding a custom webhook verifier.** The plugin owns `/dodopayments/webhooks`, signature verification, and callback dispatch.
## Resources
- [Better Auth Adapter](https://docs.dodopayments.com/developer-resources/better-auth-adaptor)
- [Dodo Payments Better Auth package](https://www.npmjs.com/package/@dodopayments/better-auth)
- [Dodo Payments SDK](https://github.com/dodopayments/dodopayments-typescript)More API Design skills
lark-event
larksuite/cli
Lark/Feishu real-time event listening / subscribing / consuming: stream events as NDJSON via `lark-cli event consume <EventKey>` (covers IM messages/reactions/chat changes, Approval status changes, Task updates, VC meeting started/joined/ended, Minutes generated, Whiteboard updated, etc.). Use for Lark bots, real-time message processing, long-running subscribers, streaming webhook/push handlers. Supports `--max-events` / `--timeout` bounded runs and a stderr ready-marker contract — designed for AI agents running as subprocesses.
lark-contact
larksuite/cli
飞书 / Lark 通讯录:按姓名 / 邮箱解析成 open_id,或按 open_id 反查姓名 / 部门 / 邮箱 / 联系方式 / 个人状态 / 签名,以及按关键词搜索当前用户可见的机器人 / 智能体(agent)。当用户提到一个名字要下一步发消息 / 排日程,或拿到 open_id 想查具体信息时使用。不负责部门树遍历、按部门列员工、组织架构图,这类需求走原生 OpenAPI。
lark-openapi-explorer
larksuite/cli
飞书/Lark 原生 OpenAPI 探索:从官方文档库中挖掘未经 CLI 封装的原生 OpenAPI 接口。当用户的需求无法被现有 lark-* skill 或 lark-cli 已注册命令满足,需要查找并调用原生飞书 OpenAPI 时使用。

