Skip to content

Webhooks

Nametag’s billing is powered by Stripe, and is only active in SaaS mode (SAAS_MODE=true). Self-hosted instances don’t process billing events. This endpoint isn’t something you call yourself, Stripe calls it, but it’s documented here for completeness and for anyone running their own SaaS-mode fork.

POST /api/webhooks/stripe

Receives billing lifecycle events from Stripe and updates the local Subscription and PaymentHistory records accordingly.

Not session or API-token authenticated. Instead, the request body is verified against the stripe-signature header using the STRIPE_WEBHOOK_SECRET environment variable. Requests with a missing or invalid signature are rejected with 400 before any data is touched.

Each event’s Stripe event ID is recorded in a unique-constrained table. A duplicate delivery (Stripe retries on timeout) is detected and skipped, returning { "received": true } without reprocessing.

EventEffect
checkout.session.completedSaves the Stripe customer ID on the user’s subscription.
customer.subscription.createdUpdates tier, status, billing frequency, and period dates. Sends a “subscription created” email.
customer.subscription.updatedSame as above. Sends a “plan changed” email if the tier changed.
customer.subscription.deletedDowngrades the subscription to the FREE tier and cancels it. Sends a cancellation email.
invoice.paidRecords a PaymentHistory entry with status SUCCEEDED.
invoice.payment_failedRecords a PaymentHistory entry with status FAILED and marks the subscription PAST_DUE.

Any other event type is logged and acknowledged without further action.

{ "received": true }

On a handler error, the endpoint returns 500 with { "error": "message" } so Stripe retries delivery.

In your Stripe dashboard, point a webhook to:

https://your-instance.example.com/api/webhooks/stripe

Subscribe it to at least: checkout.session.completed, customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, invoice.paid, invoice.payment_failed. Copy the signing secret Stripe gives you into STRIPE_WEBHOOK_SECRET.

Use the Stripe CLI to forward events to a local instance:

Terminal window
stripe listen --forward-to localhost:3000/api/webhooks/stripe

The CLI prints a webhook signing secret to use as STRIPE_WEBHOOK_SECRET while testing.