DevFlow
Docs/Integrations

Webhook integration: payload schema, HMAC, replay

Owner Theo Hayashi · Last updated 2026-04-14 · v5.4
webhookhttphmacintegrationreplay

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

bash
devflow channels add payments-webhook   --type webhook   --url https://hooks.example.com/devflow   --signing-secret PAYMENTS_WEBHOOK_SECRET

The signing secret is stored as a variables-and-secrets.

payload

json
{
  "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>.

ts
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.

Related questions

Was this helpful?
Or ask the docs bot for a follow-up — the floating button bottom-right.