Webhooks
Receive and verify job completion events.
Receiving events
Set webhook_url on job creation to receive job.completed and job.cancelled events. Each event’s data contains the job; see the payload reference.
Use a public HTTPS URL. Verify the event, queue it for processing, and return any 2xx within ten seconds. Redirects count as failures.
Verifying signatures
Verify Echo-Signature (t=<unix time>,v1=<hex HMAC>) using the submitting key’s signing secret from Billing → API.
Signatures use HMAC-SHA256 over <timestamp>.<raw body>. Use the original request bytes, compare in constant time, and reject timestamps more than five minutes away:
import hashlib, hmac, time
def verify(raw_body: bytes, signature: str, secret: str, tolerance: int = 300) -> bool:
"""signature is the Echo-Signature header: t=<unix time>,v1=<hex HMAC>."""
parts = dict(item.split("=", 1) for item in signature.split(","))
timestamp = parts["t"]
expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body,
hashlib.sha256).hexdigest()
fresh = abs(time.time() - int(timestamp)) < tolerance
return fresh and hmac.compare_digest(expected, parts["v1"])Retries and duplicates
Failed deliveries retry after 1 minute, 5 minutes, 15 minutes, 1 hour, 3 hours, 6 hours, and 12 hours: eight attempts total. After exhaustion, fetch the job with GET /jobs/{id}.
Deduplicate using the event id (also in Echo-Event-Id), which stays the same across attempts. Delivery can repeat and is not guaranteed after retries run out.