Skip to content

Webhooks

Latch uses Stripe webhooks to keep subscription state in sync.

POST /api/v1/webhooks/stripe

This route uses Stripe signature verification instead of API keys.

Stripe eventLatch behavior
checkout.session.completedCreates or updates a subscription for the existing customerId stored in checkout metadata
customer.subscription.createdStores a new Stripe-backed subscription
customer.subscription.updatedUpdates subscription state and billing period
customer.subscription.deletedMarks a subscription cancelled
invoice.payment_failedMarks a subscription past_due
invoice.payment_succeededMarks a subscription active
customer.createdUpdates stored Stripe customer metadata
customer.updatedUpdates stored Stripe customer metadata

When you save your Stripe secret key in Settings > Stripe, Latch can automatically create the webhook endpoint in your Stripe account with the correct URL and events.

If you prefer to create the webhook endpoint yourself in the Stripe Dashboard, select the events listed above and point the URL to https://latch-api.v3ck.com/api/v1/webhooks/stripe. Pass the signing secret as webhookSecret when saving Stripe settings — Latch will skip automatic creation when a manual secret is provided.

Latch validates webhook signatures with the configured Stripe webhook secret.

It can use:

  • the environment-level webhook secret (STRIPE_WEBHOOK_SECRET)
  • the per-publication stored webhook secret (auto-created or manual)

Requests with missing or invalid signatures are rejected.

When Stripe is available, webhook events are queued and retried asynchronously. If queueing is unavailable, the route falls back to synchronous handling.

Stripe statusLatch status
activeactive
trialingtrialing
past_duepast_due
canceledcancelled
unpaidpast_due

You can register HTTP endpoints in Dashboard > Webhooks to receive Latch events. Outbound payloads are JSON, signed with HMAC-SHA256 using the per-webhook secret, and delivered with up to 3 retry attempts (immediate / +10s / +60s).

Headers on each delivery:

  • X-Latch-Event — the dotted event name (e.g. subscription.created)
  • X-Latch-Signature — sha256=<hex> HMAC of the raw body

All outbound payload keys use camelCase. Stripe-incoming payloads on POST /api/v1/webhooks/stripe retain Stripe’s snake_case shape — see issue #22.

EventWhen
newsletter.subscribeA visitor passes a newsletter rule
data.submitA visitor passes a data wall rule
EventWhen
subscription.createdA subscription becomes active (manual grant or first import from Stripe)
subscription.updatedStatus, period, cancelAtPeriodEnd or trial fields change
subscription.cancelledA subscription is cancelled (immediately or at period end)
subscription.trial_endedA subscription transitions out of trialing

Sample payload:

{
"event": "subscription.created",
"data": {
"subscriptionId": "sub_…",
"customerId": "cus_…",
"priceId": "price_…",
"status": "active",
"currentPeriodStart": "2026-04-01T00:00:00.000Z",
"currentPeriodEnd": "2026-05-01T00:00:00.000Z",
"source": "manual"
},
"timestamp": "2026-04-28T12:00:00.000Z"
}
EventWhen
invoice.paidA Stripe invoice.payment_succeeded is processed
invoice.payment_failedA Stripe invoice.payment_failed is processed
invoice.upcomingStripe fires invoice.upcoming (~7 days before next invoice)
EventWhen
customer.createdPOST /api/v1/customers succeeds
customer.updatedPATCH /api/v1/customers/:id succeeds
customer.deletedDELETE /api/v1/customers/:id succeeds
EventWhen
paywall.shownSDK posts a paywall_shown track event
paywall.dismissedSDK posts a paywall_dismissed track event
paywall.convertedSDK posts a paywall_converted track event
experiment.assignedA variant impression is recorded
experiment.convertedA variant checkout completes