Webhooks
Archie Tells You When He's Done
Long jobs finish on their own schedule. Rather than poll for a result, register an endpoint and Archie posts a signed event the moment a WorkStream cycle finishes or a key changes. Every delivery is signed, so you can trust it came from him.
How Webhooks Work
You register an HTTPS endpoint and the events you care about. When one of those events happens in your organization, Archie sends a POST to your endpoint with a small JSON body describing what changed. He signs the request with a secret only the two of you share, so your endpoint can confirm the delivery is genuine before acting on it.
Endpoints belong to your organization. An event in any of your workspaces reaches the endpoints you have registered, and you choose per endpoint whether it hears about everything or just a few event types.
Register an Endpoint
Open Webhooks in your dashboard and add an endpoint. You give Archie three things: the HTTPS URL to deliver to, the event types you want, and nothing else. He generates a signing secret and shows it to you once, right after you save.
Archie only accepts public HTTPS URLs. He rejects plain HTTP and any address that resolves to a private, loopback, or internal host, and he re-checks the address every time he delivers, so an endpoint cannot be re-pointed at your internal network after the fact.
What a Delivery Looks Like
Each delivery is a POST with a JSON body and a handful of headers. The body carries the event name and a compact data object; Archie only includes the fields a third party should see, never the full internal payload.
POST /your-endpoint HTTP/1.1
Content-Type: application/json
User-Agent: Archie-Connect-Webhooks/1.0
X-Webhook-Id: 4821
X-Webhook-Event: workstream.run.completed
X-Webhook-Timestamp: 1749031200
X-Webhook-Signature: v1=8f3c…2b1d
{
"event": "workstream.run.completed",
"data": {
"run_id": "run_01J…",
"status": "completed",
"completed_at": "2026-06-04T09:20:00Z",
"duration_ms": 18342
}
}| Header | What it carries |
|---|---|
X-Webhook-Signature | The versioned HMAC, v1=<hex>. Verify this before you trust the body. |
X-Webhook-Timestamp | Unix seconds when Archie signed the request. Part of the signature, and your replay guard. |
X-Webhook-Id | The delivery id. Use it to deduplicate, since delivery is at least once. |
X-Webhook-Event | The event type, so you can route without parsing the body first. |
Verify Every Delivery
Anyone can POST to a public URL, so verify the signature before you act on a delivery. Archie signs the string {timestamp}.{raw body} with your endpoint secret using HMAC-SHA256, then sends the result as v1=<hex>. Recompute it over the exact bytes you received and compare in constant time. Reject anything signed more than five minutes ago, which stops a captured request from being replayed.
import hashlib
import hmac
import time
def verify(secret: str, body: bytes, signature: str, timestamp: str) -> bool:
# Reject deliveries older than five minutes (replay protection).
if abs(time.time() - int(timestamp)) > 300:
return False
message = f"{timestamp}.{body.decode()}".encode()
expected = "v1=" + hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)import crypto from "node:crypto";
function verify(secret, body, signature, timestamp) {
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected =
"v1=" +
crypto.createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signature);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Retries and Idempotency
Reply with any 2xx status and Archie treats the delivery as done. Anything else, or a timeout, and he retries with exponential backoff at one, two, four, eight, and sixteen seconds. After five failed attempts the delivery is set aside as dead, and you can see it in the endpoint's deliveries panel alongside the response code he got back.
Delivery is at least once, so the same event can arrive more than once after a retry. Treat X-Webhook-Id as the idempotency key and ignore an id you have already processed. Acknowledge fast: do the real work in the background rather than holding the connection open, since a slow endpoint reads as a failure and earns a retry.
Event Types
These are the events you can subscribe to. The live set is also served at GET /v1/webhooks/event-types, which is what the dashboard picker reads, so it never drifts from what Archie actually sends.
| Event | Fires when | Payload |
|---|---|---|
workstream.run.completed | A WorkStream cycle finishes successfully. | run_id, status, completed_at, duration_ms |
workstream.run.failed | A WorkStream cycle ends in failure. | run_id, status, error, completed_at |
api_key.created | A Connect key is created in your organization. | id, name, created_at |
api_key.deleted | A Connect key is deleted. | id, name |
playbook.run.completed and playbook.run.failed remain as aliases of the two WorkStream run events for endpoints that subscribed under the old names. Two more types, api_key.rotated and message.completed, are reserved and listed in the picker, but Archie does not emit them yet. Subscribe to * to receive every event type, including ones added later.