Skip to content

Commit 1136a4b

Browse files
committed
add webhook event bus for async processing
1 parent 27e172a commit 1136a4b

6 files changed

Lines changed: 607 additions & 118 deletions

File tree

packages/better-auth/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,15 @@ export { CHARGEBEE_ERROR_CODES } from "./error-codes";
212212
export type {
213213
ChargebeeOptions,
214214
ChargebeePlan,
215+
ChargebeeWebhookEventBus,
215216
Subscription,
216217
SubscriptionOptions,
217218
SubscriptionStatus,
219+
WebhookEvent,
218220
WithChargebeeCustomerId,
219221
} from "./types";
222+
export {
223+
type ChargebeeWebhookProcessor,
224+
type ChargebeeWebhookProcessorSource,
225+
createChargebeeWebhookProcessor,
226+
} from "./webhook-processor";

packages/better-auth/src/routes.ts

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ import {
2323
isActiveOrTrialing,
2424
isPendingCancel,
2525
} from "./utils";
26-
import { createWebhookHandler } from "./webhook-handler";
26+
import {
27+
createWebhookHandler,
28+
createWebhookPublishHandler,
29+
} from "./webhook-handler";
2730

2831
export function getWebhookEndpoint(options: ChargebeeOptions) {
2932
return createAuthEndpoint(
@@ -33,22 +36,30 @@ export function getWebhookEndpoint(options: ChargebeeOptions) {
3336
metadata: { isAction: false },
3437
},
3538
async (ctx) => {
36-
// Create webhook handler with better-auth context
37-
const handler = createWebhookHandler(
38-
options,
39-
{
40-
context: ctx.context as Record<string, unknown>,
41-
adapter: ctx.context.adapter as unknown as {
42-
findOne: <T = unknown>(params: unknown) => Promise<T | null>;
43-
findMany: <T = unknown>(params: unknown) => Promise<T[]>;
44-
update: (params: unknown) => Promise<unknown>;
45-
deleteMany: (params: unknown) => Promise<void>;
46-
create: (params: unknown) => Promise<unknown>;
47-
},
48-
logger: ctx.context.logger,
49-
},
50-
ctx as any,
51-
);
39+
// When an event bus is configured, validate + parse the event and
40+
// forward it to the bus (e.g. an application queue) instead of running
41+
// the DB-sync hooks inline. Otherwise process the event synchronously.
42+
const handler = options.webhookEventBus
43+
? createWebhookPublishHandler(
44+
options,
45+
options.webhookEventBus,
46+
ctx.context.logger,
47+
)
48+
: createWebhookHandler(
49+
options,
50+
{
51+
context: ctx.context as Record<string, unknown>,
52+
adapter: ctx.context.adapter as unknown as {
53+
findOne: <T = unknown>(params: unknown) => Promise<T | null>;
54+
findMany: <T = unknown>(params: unknown) => Promise<T[]>;
55+
update: (params: unknown) => Promise<unknown>;
56+
deleteMany: (params: unknown) => Promise<void>;
57+
create: (params: unknown) => Promise<unknown>;
58+
},
59+
logger: ctx.context.logger,
60+
},
61+
ctx as any,
62+
);
5263

5364
// Let user register custom event listeners on the handler
5465
options.webhookHandler?.(handler);

packages/better-auth/src/types.ts

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import type { Session, User } from "better-auth";
22
import type { Organization } from "better-auth/plugins/organization";
33
import type Chargebee from "chargebee";
44
import type {
5-
Event as ChargebeeEvent,
65
Subscription as ChargebeeSubscription,
6+
WebhookEvent as ChargebeeWebhookEvent,
77
Customer,
88
WebhookHandler,
99
} from "chargebee";
@@ -136,43 +136,68 @@ export type SubscriptionOptions = {
136136
) => Promise<boolean>;
137137
};
138138

139-
export type WebhookEvent = ChargebeeEvent;
139+
export type WebhookEvent = ChargebeeWebhookEvent;
140+
141+
/**
142+
* Event bus seam used to decouple webhook ingestion from processing.
143+
*
144+
* When provided via {@link ChargebeeOptions.webhookEventBus}, the webhook
145+
* endpoint validates and parses each incoming Chargebee event and then calls
146+
* `publish` instead of running the DB-sync hooks inline. The application is
147+
* expected to push the event onto its own queue and later process it from a
148+
* consumer using `createChargebeeWebhookProcessor`.
149+
*/
150+
export interface ChargebeeWebhookEventBus {
151+
/** Called at the HTTP endpoint for every validated, parsed event. */
152+
publish(event: WebhookEvent): Promise<void> | void;
153+
}
140154

141155
// Use native Chargebee customer creation params
142156
export type ChargebeeCustomerCreateParams = Partial<Customer.CreateInputParam>;
143157

144158
export interface ChargebeeOptions {
145-
chargebeeClient: InstanceType<typeof Chargebee>;
146-
webhookUsername?: string;
147-
webhookPassword?: string;
148-
createCustomerOnSignUp?: boolean;
149-
/**
150-
* Return additional params to pass to `cb.customer.create` for user customers.
151-
* Use this to pass fields like `first_name`, `last_name`, or any other
152-
* Chargebee customer params. The `ctx` argument is only available when the
153-
* customer is created on-demand (e.g. at subscription time), not during sign-up.
154-
*/
155-
getCustomerCreateParams?: (
156-
user: User,
157-
ctx?: Record<string, unknown>,
158-
) =>
159-
| Promise<Partial<ChargebeeCustomerCreateParams>>
160-
| Partial<ChargebeeCustomerCreateParams>;
161-
onCustomerCreate?: (params: CustomerCreateParams) => Promise<void> | void;
162-
webhookHandler?: (handler: WebhookHandler) => void;
163-
subscription?: SubscriptionOptions;
164-
organization?: {
165-
enabled: boolean;
159+
chargebeeClient: InstanceType<typeof Chargebee>;
160+
webhookUsername?: string;
161+
webhookPassword?: string;
162+
createCustomerOnSignUp?: boolean;
163+
/**
164+
* Return additional params to pass to `cb.customer.create` for user customers.
165+
* Use this to pass fields like `first_name`, `last_name`, or any other
166+
* Chargebee customer params. The `ctx` argument is only available when the
167+
* customer is created on-demand (e.g. at subscription time), not during sign-up.
168+
*/
166169
getCustomerCreateParams?: (
167-
organization: Organization & WithChargebeeCustomerId,
168-
ctx: Record<string, unknown>,
169-
) => Promise<Partial<ChargebeeCustomerCreateParams>>;
170-
onCustomerCreate?: (
171-
params: OrganizationCustomerCreateParams,
172-
ctx: Record<string, unknown>,
173-
) => Promise<void> | void;
174-
};
175-
}
170+
user: User,
171+
ctx?: Record<string, unknown>,
172+
) =>
173+
| Promise<Partial<ChargebeeCustomerCreateParams>>
174+
| Partial<ChargebeeCustomerCreateParams>;
175+
onCustomerCreate?: (params: CustomerCreateParams) => Promise<void> | void;
176+
webhookHandler?: (handler: WebhookHandler) => void;
177+
/**
178+
* Optional event bus used to decouple webhook ingestion from processing.
179+
*
180+
* When set, the webhook endpoint in the app is exptected to validate and
181+
* parses each event and calls `webhookEventBus.publish(event)` (typically pushing it onto an application
182+
* queue) instead of running the DB-sync hooks inline. Process queued events
183+
* later with `createChargebeeWebhookProcessor`.
184+
*
185+
* When not set, events are processed synchronously within the request.
186+
*/
187+
webhookEventBus?: ChargebeeWebhookEventBus;
188+
subscription?: SubscriptionOptions;
189+
organization?: {
190+
enabled: boolean;
191+
getCustomerCreateParams?: (
192+
organization: Organization & WithChargebeeCustomerId,
193+
ctx: Record<string, unknown>,
194+
) => Promise<Partial<ChargebeeCustomerCreateParams>>;
195+
onCustomerCreate?: (
196+
params: OrganizationCustomerCreateParams,
197+
ctx: Record<string, unknown>,
198+
) => Promise<void> | void;
199+
};
200+
}
176201

177202
export interface Subscription {
178203
id: string;

0 commit comments

Comments
 (0)