Webhook events
The delivery envelope, signature headers, and event types.
Everything Stacklink delivers to your project webhook uses one envelope and one signature scheme. Receiving and configuring is covered in Webhooks; this is the wire reference.
Event types
| Type | Fired when |
|---|---|
stacklink.trigger.event | A subscribed provider event was ingested and matched. |
stacklink.connected_account.expired | An account's credentials expired and can't be refreshed. |
stacklink.trigger.disabled | A subscription was disabled (e.g. repeated polling failures). |
Envelope
{
"version": "v1",
"id": "evt_...",
"type": "stacklink.trigger.event",
"metadata": {
"trigger_id": "...",
"trigger_slug": "...",
"connected_account_id": "...",
"tenant_id": "...",
"provider_key": "...",
"auth_config_id": "...",
"raw_event_id": "..."
},
"data": { /* normalized provider payload */ },
"timestamp": "2026-05-08T05:30:00+05:30"
}Signature headers
| Header | Content |
|---|---|
webhook-id | The delivery id. |
webhook-timestamp | Unix seconds — reject if outside your tolerance (default 300 s). |
webhook-signature | v1,<base64 HMAC-SHA256> over `${id}.${timestamp}.${body}`. |
import { verifyWebhookSignature } from 'stacklink/sdk';
const ok = await verifyWebhookSignature({
id: req.headers['webhook-id'],
timestamp: req.headers['webhook-timestamp'],
signature: req.headers['webhook-signature'],
body: rawBody, // the exact raw bytes
secret: process.env.STACKLINK_WEBHOOK_SECRET!,
});The comparison is constant-time, multiple v1, candidates in one header are supported (secret
rotation), and timestamps outside tolerance fail closed.
Delivery behavior
Failed deliveries retry with backoff and land in a dead-letter queue after max attempts. Inspect
and recover via GET /v1/logs/webhook-deliveries, POST .../retry, redrive, and replay — or the
SDK's stacklink.webhooks.redriveDeadLetters() / replayEvent().
Always verify before trusting a delivery, and always read the raw request body — re-serialized JSON will not match the signature.