> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useplinth.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks & events

> We send a signed webhook every time something important happens — subscription renewed, payment failed, plan changed.

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](https://app.useplinth.com/dashboard/webhooks) or via the
[API](/api-reference/webhooks):

```bash theme={null}
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](/guides/receiving-webhooks).

## Webhook payload shape

Every event uses the same envelope. Switch on `type` and read `data.object`:

```json theme={null}
{
  "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](/webhook-events).
* `created` — unix seconds when the event occurred.
* `data.object` — the event-specific payload; shape varies by type.

<Note>
  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.
</Note>

## Full event catalog

See [Webhook events](/webhook-events) for every event type, what triggers it, and a payload example.
