Webhooks
Event types, payload shape, signature verification, and delivery semantics.
Webhooks let Tarqa tell your server when something happens, such as a failed API call or a new key, by sending a POST to an HTTPS URL you choose.
Available on the Pro, Team and Enterprise plans. Add an endpoint in Developer → Webhooks or with POST /v1/webhooks. The signing secret is shown once, when you create it.
Set one up
- 1Create the endpointIn the dashboard (or via the API), enter your URL and pick the events you want. Copy the signing secret.
- 2Verify every deliveryCheck the signature with your secret (code below) and reject anything that doesn't match.
- 3Reply fastReturn any
2xxwithin 10 seconds and do the real work in the background. - 4Send a testClick Test (or
POST /v1/webhooks/:id/test) to receive awebhook.testevent.
Events
| Event | Fires when | data fields |
|---|---|---|
api.call.succeeded | An API request finishes with 2xx | endpoint, method, status_code, model, timestamp |
api.call.failed | An API request finishes with non-2xx | endpoint, method, status_code, model, timestamp |
key.created | A new API key is created | key_id, name, key_prefix, created_at |
team.member.invited | Someone is invited to your team | email, role, team_id, invited_at |
webhook.test | You send a test delivery | message, webhook_id, user_id |
What we send
Every delivery is a POST with this body:
{
"id": "d3f1c8a2-5b6e-4c11-9a0f-7e2d4b8c1a90",
"event": "api.call.succeeded",
"data": { "endpoint": "/v1/chat/completions", "method": "POST", "status_code": 200, "model": "claude-opus-4-6", "timestamp": "2026-09-03T10:30:00.000Z" },
"webhook_id": "b1a2c3d4-...",
"timestamp": "2026-09-03T10:30:00.123Z"
}| Field | Meaning |
|---|---|
id | Unique per delivery and the same across retries of that delivery. Dedupe on this. |
webhook_id | Your endpoint's ID. The same for every delivery to this URL, so it is not a dedupe key. |
timestamp | When the delivery was created (ISO 8601). |
And these headers:
| Header | Value |
|---|---|
X-Webhook-Signature | HMAC-SHA256 of the raw body, hex, no prefix |
X-Webhook-Event | The event name |
X-Webhook-Id | Same as id: the dedupe key |
X-Webhook-Timestamp | Same as timestamp |
User-Agent | TarqaAI-Webhook/1.0 |
Verify the signature
Compute HMAC-SHA256 over the exact raw body bytes with your secret, and compare it to X-Webhook-Signature in constant time. Also reject deliveries older than about 5 minutes.
import crypto from 'crypto';
// Needs the raw body: express.raw({ type: 'application/json' })
function verify(req, secret) {
const expected = crypto.createHmac('sha256', secret).update(req.body).digest('hex');
const got = req.get('X-Webhook-Signature') || '';
return expected.length === got.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(got));
}
app.post('/tarqa-webhook', express.raw({ type: 'application/json' }), (req, res) => {
if (!verify(req, process.env.TARQA_WEBHOOK_SECRET)) return res.sendStatus(401);
const ts = Date.parse(req.get('X-Webhook-Timestamp'));
if (Math.abs(Date.now() - ts) > 5 * 60 * 1000) return res.sendStatus(401); // too old
const event = JSON.parse(req.body.toString());
if (!seen(event.id)) enqueue(event); // dedupe, then process in the background
res.sendStatus(200); // reply fast
});POST /v1/webhooks/:id/rotate creates a new secret and returns it once. The old one stops working immediately, so deploy the new secret first.
How delivery works
| Rule | Details |
|---|---|
| At least once | A delivery can arrive more than once (e.g. you processed it but timed out). Dedupe on X-Webhook-Id. |
| Retries | Up to 4 attempts: the first send, then retries after 1s, 5s and 15s. Retries survive a restart on our side. |
| Timeout | Each attempt waits 10 seconds. Slower counts as failed and is retried, so reply first and work later. |
| Success | Any 2xx. Your response body is saved in the delivery log but not interpreted. |
| Order | Not guaranteed. A retried older event can arrive after a newer one; use timestamp if order matters. |
| Auto-disable | After 15 failed deliveries in a row the endpoint is switched off. Re-enable it in the dashboard once it's fixed. |
| Replay protection | Up to you: check X-Webhook-Timestamp against a window (5 minutes is reasonable). We don't reject replays. |
Debug a failing endpoint
GET /v1/webhooks/:id/deliveries lists recent attempts with status code, a response snippet, attempt count and error, so you can fix it before it auto-disables.
FAQ
I lost my signing secret.
POST /v1/webhooks/:id/rotate (or in the dashboard) and deploy the new one.Why did I get the same event twice?
id values you've handled and skip repeats.