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

# Webhooks

> How Tanvik delivers events to your server

Webhooks let your server react in real time to things happening on Tanvik — an inbound WhatsApp message, a delivery status update, an OTP verification, a template review result — without polling the API.

## Registering an endpoint

```bash theme={null}
curl https://xwialxkobwygraiddimj.supabase.co/functions/v1/public-api/v1/webhooks \
  -H "Authorization: Bearer tvk_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/tanvik",
    "events": ["message.received", "message.status", "otp.verified"]
  }'
```

### Request body

<ParamField body="url" type="string" required>
  Your endpoint. Must be `https://`.
</ParamField>

<ParamField body="events" type="string[]" required>
  One or more of `message.received`, `message.status`, `otp.verified`, `template.status`.
</ParamField>

### Response

```json theme={null}
{
  "webhook_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "url": "https://yourapp.com/webhooks/tanvik",
  "events": ["message.received", "message.status", "otp.verified"],
  "secret": "whsec_9f2ac1e460a040bc...",
  "status": "active"
}
```

<Warning>
  The `secret` is shown exactly once, at registration. Store it — you'll need it to verify signatures, and Tanvik can't show it to you again.
</Warning>

You can register multiple endpoints, each subscribed to a different subset of events.

## Listing and removing webhooks

```bash theme={null}
# List
curl https://xwialxkobwygraiddimj.supabase.co/functions/v1/public-api/v1/webhooks \
  -H "Authorization: Bearer tvk_live_xxxxxxxxxxxxxxxx"

# Remove
curl -X DELETE https://xwialxkobwygraiddimj.supabase.co/functions/v1/public-api/v1/webhooks/{webhook_id} \
  -H "Authorization: Bearer tvk_live_xxxxxxxxxxxxxxxx"
```

The list response never includes the secret — only `id`, `url`, `events`, `status`, and timestamps.

## Verifying signatures

Every webhook request includes an `X-Tanvik-Signature` header — an HMAC-SHA256 signature of the raw request body, signed with the secret you got at registration.

```javascript theme={null}
const crypto = require('crypto');

function isValidSignature(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(`sha256=${expected}`));
}
```

<Warning>
  Always verify the signature before trusting a webhook payload. Compute it over the raw request body, not a re-serialized JSON object.
</Warning>

## Delivery and retries

* Your endpoint must respond with a `2xx` status within 10 seconds.
* Failed deliveries are retried: 1 minute, 5 minutes, 30 minutes, then hourly.
* After 24 hours of a delivery still failing, that endpoint is automatically set to `paused` — check `GET /v1/webhooks` to see `status` and `pause_reason`. Re-registering (or a future re-activate endpoint) is needed to resume it.

## Event types

| Event              | Fired when                                                    |
| ------------------ | ------------------------------------------------------------- |
| `message.received` | A customer sends you a WhatsApp message                       |
| `message.status`   | A sent message changes status (sent, delivered, read, failed) |
| `otp.verified`     | A customer successfully verifies an OTP                       |
| `template.status`  | A submitted template is approved or rejected by Meta          |

See the [WhatsApp Webhook](/whatsapp/webhook) page for the exact payload shape of each event.

<Note>
  Webhooks are polled into your account continuously for messages and OTPs. `template.status` specifically depends on Tanvik's template sync running for your account — if you need it to fire faster after a Meta review, ask your account team.
</Note>
