Webhooks
Latch uses Stripe webhooks to keep subscription state in sync.
Endpoint
Section titled “Endpoint”POST /api/v1/webhooks/stripeThis route uses Stripe signature verification instead of API keys.
Supported events
Section titled “Supported events”| Stripe event | Latch behavior |
|---|---|
checkout.session.completed | Creates or updates a subscription for the existing customerId stored in checkout metadata |
customer.subscription.created | Stores a new Stripe-backed subscription |
customer.subscription.updated | Updates subscription state and billing period |
customer.subscription.deleted | Marks a subscription cancelled |
invoice.payment_failed | Marks a subscription past_due |
invoice.payment_succeeded | Marks a subscription active |
customer.created | Updates stored Stripe customer metadata |
customer.updated | Updates 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.
Manual setup
Section titled “Manual setup”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.
Signature verification
Section titled “Signature verification”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.
Idempotency and retries
Section titled “Idempotency and retries”When Stripe is available, webhook events are queued and retried asynchronously. If queueing is unavailable, the route falls back to synchronous handling.
Subscription status mapping
Section titled “Subscription status mapping”| Stripe status | Latch status |
|---|---|
active | active |
trialing | trialing |
past_due | past_due |
canceled | cancelled |
unpaid | past_due |
Outbound webhooks
Section titled “Outbound webhooks”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.
Available events
Section titled “Available events”| Event | When |
|---|---|
newsletter.subscribe | A visitor passes a newsletter rule |
data.submit | A visitor passes a data wall rule |
Subscription lifecycle
Section titled “Subscription lifecycle”| Event | When |
|---|---|
subscription.created | A subscription becomes active (manual grant or first import from Stripe) |
subscription.updated | Status, period, cancelAtPeriodEnd or trial fields change |
subscription.cancelled | A subscription is cancelled (immediately or at period end) |
subscription.trial_ended | A 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"}Invoice + payment
Section titled “Invoice + payment”| Event | When |
|---|---|
invoice.paid | A Stripe invoice.payment_succeeded is processed |
invoice.payment_failed | A Stripe invoice.payment_failed is processed |
invoice.upcoming | Stripe fires invoice.upcoming (~7 days before next invoice) |
Customer lifecycle
Section titled “Customer lifecycle”| Event | When |
|---|---|
customer.created | POST /api/v1/customers succeeds |
customer.updated | PATCH /api/v1/customers/:id succeeds |
customer.deleted | DELETE /api/v1/customers/:id succeeds |
Paywall + experiment
Section titled “Paywall + experiment”| Event | When |
|---|---|
paywall.shown | SDK posts a paywall_shown track event |
paywall.dismissed | SDK posts a paywall_dismissed track event |
paywall.converted | SDK posts a paywall_converted track event |
experiment.assigned | A variant impression is recorded |
experiment.converted | A variant checkout completes |