Skip to main content
Polling is slow and misses things. Webhooks are instant. Every state change in the billing engine is written to a transactional outbox and delivered to your endpoints — subscriptions renewing, cards failing, transfers matching, grace periods expiring.

Set up your endpoint

Register a webhook URL in the dashboard or via the API:
curl -X POST https://api.useplinth.com/v1/webhook-endpoints \
  -H "Authorization: Bearer sk_test_DOCS_SHARED_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://yourapp.com/api/plinth/webhook" }'
The response includes a signing secret (whsec_…), shown once. Optionally pass event_types to subscribe to a subset; omit it (or pass []) to receive everything.

Signature verification

Every request carries a Plinth-Signature: t=<unix>,v1=<hmac> header — an HMAC-SHA256 of "<t>.<raw body>" keyed by your endpoint secret. Verify it against the raw body, before parsing JSON, and reject anything that doesn’t match. The full copy-paste snippet (Next.js) lives in Receiving webhooks.

Webhook payload shape

Every event uses the same envelope. Switch on type and read data.object:
{
  "id": "evt_01JXYZ...",
  "object": "event",
  "api_version": "2026-01-01",
  "type": "subscription.activated",
  "created": 1782950400,
  "data": { "object": { "subscriptionId": "sub_01JABC...", "customerId": "cus_01JXYZ..." } }
}
  • id — unique event ID. Treat it as an idempotency key — ignore an id you’ve already processed.
  • type — the event name. See the full catalog.
  • created — unix seconds when the event occurred.
  • data.object — the event-specific payload; shape varies by type.
We retry a failed delivery (non-2xx or timeout) with exponential backoff — up to 8 attempts — then mark it failed. Return a 2xx quickly to acknowledge; inspect and resend from Webhooks → deliveries in the dashboard.

Full event catalog

See Webhook events for every event type, what triggers it, and a payload example.