Inbound Webhooks
Swayzio ingests events from three external providers. Each webhook verifies a provider signature against the raw request body and fails closed — a missing secret or invalid signature is rejected, never waved through.
| Provider | Path | Verification | Service |
|---|---|---|---|
| Stripe | POST /api/swayzio/v1/webhooks/stripe | stripe-signature (constructEvent) | Next (native only) |
| Dub | POST /api/swayzio/v1/webhooks/dub | Dub-Signature HMAC‑SHA256 | Next (native only) |
| Resend | POST /api/swayzio/v1/webhooks/resend | Svix (svix-id / svix-timestamp / svix-signature) | Fastify Core API |
These endpoints are meant to be called by the providers, not by integrators. They’re documented here so operators understand what’s verified and what’s persisted.
Stripe — billing events
POST /api/swayzio/v1/webhooks/stripe
- Verification:
stripe.webhooks.constructEvent(rawBody, stripe-signature, STRIPE_WEBHOOK_SECRET). Missing header →400; invalid signature →400 stripe_signature_invalid. Also requires the Vercel runtime (503 native_runtime_requiredotherwise). - Events handled:
checkout.session.completed,customer.subscription.created/updated/deleted, plus others routed inapplyStripeEvent. - Persists: subscription / entitlement state and
billing_eventsrows. Acheckout.session.completedwhose owner can’t be resolved fires a Sentry warning (stripe_webhook_unresolved_owner) rather than silently dropping. - Conversions: completed checkouts also drive Dub sale attribution (
track/sale) when the subscriber arrived via a share link. See Analytics. - Response:
{ received, status, type }.
Dub — link / click / conversion events
POST /api/swayzio/v1/webhooks/dub
- Verification: HMAC‑SHA256 of the raw body compared (timing‑safe) against the
Dub-Signatureheader usingDUB_WEBHOOK_SECRET. Missing secret or signature →401. - Events:
link.clicked,lead.created,sale.created(and link lifecycle events). - Persists: sanitized, PII‑free event rows into
external_tracking_events, mapped to the owning pack/track link. Geo/device/referer‑domain only — never IP, user‑agent, or customer identity. Requires a DB pool (503otherwise). - Response:
{ ok: true, … }.
The Dub webhook is optional — the Analytics surface reads Dub’s API live and works without it. It only adds a redundant local copy of click data for resilience.
Resend — email delivery status
POST /api/swayzio/v1/webhooks/resend (Fastify Core API)
- Verification: Svix signature against
RESEND_WEBHOOK_SECRET, using a raw‑body content‑type parser so the HMAC sees the exact bytes. Missing secret →503; invalid signature →401; bad payload →400. - Events:
email.sent/delivered/delivery_delayed/bounced/complained/opened/clicked/failed/suppressed. - Persists: maps the event to a
PackInvitationDeliveryStatusand updates the pack‑invitation delivery row by provider email id. - Response:
{ received, deliveryUpdated, … }.