sentinel
Webhook integration
Receive signed JSON alerts at your own endpoint
Endpoint requirements
- HTTPS only
- a public hostname
- redirects are not followed
- 3xx counts as failed delivery
- respond with 200 within 10 seconds
The payload shape
{
"version": 1,
"id": "3f6d3f0e-6a4e-4b9c-9a2e-7f1d2c8b5a90",
"event": "overdue",
"sent_at": "2026-07-16T04:01:00.000Z",
"monitor": {
"name": "acme-production",
"slug": "0a1b2c3d-4e5f-6789-abcd-ef0123456789",
"url": "https://boringbackup.com/monitors/0a1b2c3d-…",
"schedule": "0 3 * * *",
"timezone": "Europe/Sofia",
"grace_seconds": 3600
},
"last_ping_at": "2026-07-14T03:00:12.000Z"
}Event kinds
| event | fired when |
|---|---|
| overdue | no good backup by deadline + grace |
| recovered | a good backup landed while overdue |
| test | you pressed the test button |
Verifying the signature
Every request carries a timestamp and an HMAC of it plus the body — verify both, reject stale timestamps, and a captured request can't be replayed. The secret is shown only on create. If lost, it can be rotated.
X-Sentinel-Timestamp: 1784167260
X-Sentinel-Signature: sha256=<hex hmac> The signature is HMAC-SHA256 over "{timestamp}.{body}" keyed with the integration's
secret:
# Rails
body = request.body.read
timestamp = request.headers["X-Sentinel-Timestamp"]
signature = request.headers["X-Sentinel-Signature"]
expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", secret, "#{timestamp}.#{body}")
head :unauthorized unless ActiveSupport::SecurityUtils.secure_compare(expected, signature)
head :unauthorized if (Time.now.to_i - timestamp.to_i).abs > 300Retries & idempotency
Webhooks are retried up to 5 times with a minute cooldown between attempts.
Use theid field as an idempotent key.