Webhooks and event catalog
Gripz sends a signed HTTPS request to your endpoint when something changes. Delivery is at least once, so use the event ID to ignore repeats.
Events
{
"id": "evt_01J9ZK4T7Q",
"topic": "conversation.created",
"api_version": "2026-09-01",
"created_at": "2026-10-02T10:42:11Z",
"workspace_id": "tw_8f2k",
"ordering_key": "cnv_7Hq2Lm",
"sequence": 1,
"data": {
"id": "cnv_7Hq2Lm",
"object": "conversation",
"state": "open",
"channel": "chat",
"subject": "Invoice PDF shows the wrong tax rate",
"contact": { "id": "ct_9d3m" }
}
}Verify the signature
Every delivery has an ID, a timestamp and an HMAC-SHA256 signature over the ID, the timestamp and the raw body. Reject anything older than five minutes.
import crypto from 'node:crypto';
export function verifyGripzWebhook(req, secret) {
const id = req.headers['gripz-webhook-id'];
const timestamp = req.headers['gripz-webhook-timestamp'];
const signature = req.headers['gripz-webhook-signature'];
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (age > 300) return false; // older than five minutes
const expected = crypto
.createHmac('sha256', secret)
.update(`${id}.${timestamp}.${req.rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}Retry policy
Any 2xx answer within 10 seconds counts as delivered. Anything else is retried on this schedule for 72 hours.
| Attempt | Sent |
|---|---|
| 1 | Immediately |
| 2 | 1 minute after the first attempt |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 6 hours |
| 7 and later | Every 6 hours, until 72 hours after the first attempt |
After 72 hours of failures the subscription is paused and admins are notified. Undelivered events wait 14 days in the dead-letter queue so you can replay them. A 410 Gone answer disables the subscription at once.