How this works
You register an HTTPS URL and choose which event types it should receive. When a matching event occurs in your workspace, the platform sends an HTTP POST to your URL with a JSON payload and a signature header. Your receiver verifies the signature, acknowledges with any 2xx status within 5 seconds, and processes the event.
Each endpoint has its own signing secret, prefixed ewhsec_. The secret is shown when the endpoint is created and can be rotated at any time from the console; rotation invalidates the previous secret immediately.
Events we send
| Event | Fires when |
|---|---|
workspace.provisioned | A developer workspace (organization and project) is created. |
api_key.created | A new API key is issued in the project. |
api_key.rotated | An API key is rotated; the old secret stops working. |
api_key.revoked | An API key is revoked. |
budget.updated | The project budget or hard cap changes. |
developer.test | You trigger a test delivery from the console. Test events are real deliveries through the full signing path, so a passing test proves your verification code. |
This is the complete current catalog. Inference-level events (per-request notifications) are deliberately not webhook events: at production volume they would be a firehose, and the Usage API is the right way to observe request activity.
What we send you
Every delivery is an HTTP POST with Content-Type: application/json and this envelope:
{
"id": "5f8c2b1e-...", // event id (UUID)
"type": "api_key.rotated", // event type from the table above
"created_at": "2026-07-28T09:30:00Z",
"organization_id": "...",
"project_id": "...",
"actor_user_id": "...", // the user who caused it, when known
"summary": "API key rotated", // human-readable one-liner
"data": { ... } // event-specific detail
}Alongside the body, every delivery carries these headers:
| Header | Meaning |
|---|---|
x-evaesi-event | Event type, matching type in the body. |
x-evaesi-event-id | Event UUID. The same event keeps its id across retries; deduplicate on this. |
x-evaesi-webhook-id | The endpoint receiving the delivery. |
x-evaesi-delivery-id | Unique per delivery attempt. An input to the signature. |
x-evaesi-attempt-number | 1 for the original delivery, incrementing on each retry. |
x-evaesi-signature | Signature to verify, format below. |
Verifying Signatures
The x-evaesi-signature header has the form t={timestamp},v1={hex}. The signed content is the timestamp, the delivery id, and the raw request body, joined with dots: {timestamp}.{delivery_id}.{payload}. The signature is HMAC-SHA256 keyed with your endpoint secret. Because the delivery id is inside the signed content, a captured payload cannot be replayed against you under a different delivery.
import hmac, hashlib, time
def verify_delivery(headers, raw_body: bytes, secret: str,
tolerance_seconds: int = 300) -> bool:
signature_header = headers["x-evaesi-signature"]
delivery_id = headers["x-evaesi-delivery-id"]
parts = dict(p.split("=", 1) for p in signature_header.split(","))
timestamp, expected = parts["t"], parts["v1"]
# Reject stale deliveries to bound replay windows.
if abs(time.time() - int(timestamp)) > tolerance_seconds:
return False
signed = f"{timestamp}.{delivery_id}.".encode() + raw_body
digest = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(digest, expected)Verify against the raw request bytes, not a re-serialised JSON object: any reordering or whitespace change breaks the digest. Always use a constant-time comparison, as above.
Retries & Replays
A delivery is attempted once, when the event occurs, with a 5-second timeout. Any 2xx response marks it delivered; anything else, or a timeout, records the failure with the response excerpt on the delivery record. There is no automatic retry loop today, and this page will say so until there is one.
Failed deliveries are retried manually from the console's delivery history. A retry is a new delivery (new x-evaesi-delivery-id, incremented x-evaesi-attempt-number) of the same event (x-evaesi-event-id unchanged), re-signed for the new delivery. Every delivery record keeps the SHA-256 of the payload it sent, so what was actually transmitted is auditable later.
Managing endpoints
Endpoints are managed from the developer console: create with a chosen event subscription, edit the URL or subscription, disable, rotate the secret, send a test event, and browse delivery history with per-attempt outcomes. The console family lives under /v1/developer/webhooks and authenticates with your browser session, not an API key: webhook management is an account-holder action, and an API key leak must not let an attacker redirect your event stream.
The console opens with the platform launch; the webhooks page shows the coming-soon screen until then.