Webhook Integration Guide ¶
Reference for configuring and consuming webhooks from the Relay event pipeline. All payloads are JSON over HTTPS with HMAC-SHA256 signatures.
Authentication ¶
Every webhook request includes a signature header for verification:
X-Relay-Signature: sha256=a1b2c3d4e5f6...
Verify the signature against your endpoint secret:
import hmac
import hashlib
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
Reject any request where the signature does not match. Replay attacks are mitigated by the timestamp field in the payload — reject events older than 5 minutes.
Event Types ¶
| Event | Trigger | Retry Policy |
|---|---|---|
pipeline.started |
Pipeline execution begins | 3 attempts, exponential backoff |
pipeline.completed |
All stages finished successfully | 3 attempts, exponential backoff |
pipeline.failed |
Any stage exits with non-zero code | 5 attempts, 30s intervals |
stage.started |
Individual stage begins execution | No retry |
stage.completed |
Individual stage finished | No retry |
artifact.created |
Build artifact uploaded to storage | 3 attempts, exponential backoff |
deployment.promoted |
Artifact promoted to production | 5 attempts, 30s intervals |
Payload Structure ¶
All events share a common envelope:
{
"id": "evt_a1b2c3d4",
"type": "pipeline.completed",
"timestamp": "2026-03-10T14:32:00Z",
"project_id": "proj_x7y8z9",
"data": {
"pipeline_id": "pipe_m4n5o6",
"duration_ms": 34521,
"stages": [
{
"name": "build",
"status": "passed",
"duration_ms": 18200
},
{
"name": "test",
"status": "passed",
"duration_ms": 12100
},
{
"name": "deploy",
"status": "passed",
"duration_ms": 4221
}
],
"commit": {
"sha": "e7f8a9b0c1d2",
"message": "fix: resolve race condition in queue consumer",
"author": "alex@relay.dev"
}
}
}
Endpoint Configuration ¶
Register a webhook endpoint via the API:
curl -X POST https://api.relay.dev/v1/webhooks \
-H "Authorization: Bearer $RELAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/relay",
"events": ["pipeline.completed", "pipeline.failed"],
"secret": "whsec_your_signing_secret"
}'
Response:
{
"id": "wh_p1q2r3",
"url": "https://your-app.com/webhooks/relay",
"events": ["pipeline.completed", "pipeline.failed"],
"status": "active",
"created_at": "2026-03-10T14:00:00Z"
}
Handling Failures ¶
Your endpoint must return a 2xx status code within 10 seconds. Any other response triggers the retry policy for that event type.
| Response | Behavior |
|---|---|
200–299 |
Event acknowledged, no retry |
301–399 |
Redirects are not followed |
400–499 |
Client error, retried per policy |
500–599 |
Server error, retried per policy |
| Timeout | No response in 10s, retried per policy |
If all retries are exhausted, the event is written to the dead letter queue. Retrieve failed events with:
curl https://api.relay.dev/v1/webhooks/wh_p1q2r3/dead-letter \
-H "Authorization: Bearer $RELAY_API_KEY"
Rate Limits ¶
| Tier | Max Events/min | Max Endpoints |
|---|---|---|
| Free | 60 | 2 |
| Pro | 600 | 10 |
| Enterprise | 6,000 | Unlimited |
Events that exceed your rate limit are queued and delivered with a best-effort delay of up to 60 seconds.
Testing ¶
Send a test event to your endpoint without triggering a real pipeline:
curl -X POST https://api.relay.dev/v1/webhooks/wh_p1q2r3/test \
-H "Authorization: Bearer $RELAY_API_KEY" \
-d '{"type": "pipeline.completed"}'
The test payload uses synthetic data and includes "test": true in the envelope. Your handler should check for this flag and skip any production side effects.