learning_ai_common_plat/services/platform-service/src/lib/webhooks.ts
saravanakumardb1 2692c918ce feat(waitlist): add pre-launch waitlist module (types, repo, routes)
- Create waitlist/types.ts: WaitlistEntryDoc, Zod schemas for join/status/unsubscribe/admin
- Create waitlist/repository.ts: full CRUD, dedup by emailNormalized, position assignment, stats
- Create waitlist/routes.ts: 5 public endpoints + 7 admin endpoints with role guard
- Add waitlist container to cosmos-init.ts (+ 13 previously missing containers)
- Add dispatchWaitlistJoined webhook to webhooks.ts
- Register waitlistRoutes in server.ts
- Public: join, check status, count, config, unsubscribe
- Admin: list, stats, get, update, delete, batch invite, CSV export
2026-02-16 22:45:14 -08:00

69 lines
2.2 KiB
TypeScript

/**
* Webhook dispatcher — fire-and-forget POST to a configurable callback URL.
*
* Products register webhook URLs via env vars:
* WEBHOOK_INVITATION_REDEEMED_URL — called after an invitation code is redeemed
* WEBHOOK_REFERRAL_STATUS_URL — called when a referral status transitions
* WEBHOOK_WAITLIST_JOINED_URL — called when someone joins a pre-launch waitlist
*
* Payloads are JSON; failures are logged but never block the caller.
*/
import { DEFAULT_PRODUCT_ID } from './product-config.js';
export interface WebhookPayload {
event: string;
productId: string;
timestamp: string;
data: Record<string, unknown>;
}
/**
* POST a webhook payload to the given URL. Fire-and-forget: errors are logged,
* never thrown. Returns true if the POST succeeded (2xx), false otherwise.
*/
export async function dispatchWebhook(
url: string | undefined,
event: string,
data: Record<string, unknown>,
productId?: string
): Promise<boolean> {
if (!url) return false;
const payload: WebhookPayload = {
event,
productId: productId ?? DEFAULT_PRODUCT_ID,
timestamp: new Date().toISOString(),
data,
};
try {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
signal: AbortSignal.timeout(5_000),
});
return res.ok;
} catch (err) {
// Log but never block — use stderr for structured output
process.stderr.write(`[webhook] Failed to dispatch ${event} to ${url}: ${err}\n`);
return false;
}
}
/** Dispatch an invitation.redeemed webhook. */
export function dispatchInvitationRedeemed(data: Record<string, unknown>): Promise<boolean> {
return dispatchWebhook(process.env.WEBHOOK_INVITATION_REDEEMED_URL, 'invitation.redeemed', data);
}
/** Dispatch a referral.status_changed webhook. */
export function dispatchReferralStatusChanged(data: Record<string, unknown>): Promise<boolean> {
return dispatchWebhook(process.env.WEBHOOK_REFERRAL_STATUS_URL, 'referral.status_changed', data);
}
/** Dispatch a waitlist.joined webhook. */
export function dispatchWaitlistJoined(data: Record<string, unknown>): Promise<boolean> {
return dispatchWebhook(process.env.WEBHOOK_WAITLIST_JOINED_URL, 'waitlist.joined', data);
}