ArrowLeftDeveloper Portal
Webhooks
Receive real-time HTTP notifications when events happen on Cantarell OS.
SettingsHow webhooks work
- Register a webhook URL and select event types (or use
*for all) - When an event occurs, we POST a JSON payload to your URL
- We include an HMAC-SHA256 signature in the
X-Cantarell-Signature-256header - Respond with 2xx within 10 seconds to acknowledge receipt
- On failure, we retry with exponential backoff: 1m, 5m, 30m, 2h, 24h
- After 5 consecutive failures, the subscription is paused (circuit breaker)
FileJsonPayload structure
{
"event": "order.created",
"event_id": "evt_a1b2c3d4",
"organization_id": "org_xyz789",
"data": {
"id": "ORD-2024-001",
"product": "Magna",
"volume_liters": 10000,
"total_mxn": 225000.00,
"status": "created"
},
"created_at": "2026-03-07T10:00:00Z"
}Request headers
| Header | Description |
|---|---|
| X-Cantarell-Signature-256 | HMAC-SHA256 hex digest |
| X-Cantarell-Timestamp | Unix timestamp (for replay prevention) |
| X-Cantarell-Delivery-Id | Unique delivery ID (for idempotency) |
| X-Cantarell-Event | Event type (e.g., order.created) |
Signature verification
Always verify the signature before processing a webhook to prevent spoofing.
import hmac, hashlib, time
def verify_webhook(secret, payload_body, signature, timestamp, tolerance=300):
"""Verify a Cantarell OS webhook signature."""
now = int(time.time())
if abs(now - int(timestamp)) > tolerance:
return False # Replay attack protection
signed_content = f"{timestamp}.{payload_body}"
expected = hmac.new(
secret.encode(), signed_content.encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# In your webhook handler:
# signature = request.headers["X-Cantarell-Signature-256"]
# timestamp = request.headers["X-Cantarell-Timestamp"]
# is_valid = verify_webhook(secret, request.body, signature, timestamp)Event catalog
RepeatRetry policy
| Attempt | Delay | Total elapsed |
|---|---|---|
| 1st retry | 1 minute | ~1 min |
| 2nd retry | 5 minutes | ~6 min |
| 3rd retry | 30 minutes | ~36 min |
| 4th retry | 2 hours | ~2.5 hours |
| 5th retry (final) | 24 hours | ~26.5 hours |
Circuit Breaker Enabled
After 5 consecutive failures, the subscription is automatically paused to prevent cascading failures. You can manually reactivate it from the developer dashboard once your endpoint is healthy.