Webhook integration: payload schema, HMAC, replay
Webhook integration
The webhook channel is the escape hatch — anything we don't have a first-class integration for, you can wire up with a webhook.
creating a webhook channel
devflow channels add payments-webhook --type webhook --url https://hooks.example.com/devflow --signing-secret PAYMENTS_WEBHOOK_SECRETThe signing secret is stored as a variables-and-secrets.
payload
{
"id": "evt_01HK4MRWBC...",
"version": "1.2",
"event_type": "incident.opened",
"timestamp": "2026-04-15T14:32:11.823Z",
"incident": {
"id": "inc_01HK4...",
"monitor": "payments-api-charge",
"severity": "critical",
"regions_failing": ["us-east-1", "eu-west-1"],
"started_at": "2026-04-15T14:32:11.000Z",
"assertion_failed": "latency_lt_ms(800)",
"url": "https://app.devflow.io/incidents/inc_01HK4..."
}
}event_type is one of: incident.opened, incident.acknowledged, incident.resolved, monitor.failed, monitor.recovered, slo.burn_rate_breach.
HMAC verification
We sign every webhook with HMAC-SHA256 over the body using the signing secret. The signature is sent as DF-Signature: t=<timestamp>,v1=<hex-mac>.
import crypto from 'node:crypto';
function verify(rawBody: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(header.split(',').map(s => s.split('=')));
const expected = crypto
.createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}Reject if the timestamp is older than 5 minutes or the signature doesn't match.
replay
The dashboard has a "Replay" button on every webhook event in the last 24 hours. Useful when your handler had a bug and you've fixed it.
retries
We retry up to 6 times with exponential backoff over ~10 minutes if your endpoint returns non-2xx. After that we stop and the dashboard surfaces the failure.
versioning
The schema version is in version. We add fields without bumping; we bump major on breaking changes and run both schemas for at least 90 days. Subscribe to /changelog for announcements.