Webhooks
PacSpace tells your endpoint when an entry commits and when a queued entry fails to commit. One endpoint per environment, signed deliveries, and a 72-hour retry schedule.
A write is answered as queued; committing comes next. Webhooks are how your code learns the outcome without polling: record.committed when the entry is in the record, record.failed when a queued entry could not be committed. A write that did not land is always reported.
Say an evaluation harness writes each action agent-7 takes during evaluation run 4417 to the record eval-run-4417. The API answers each write with QUEUED, and the harness moves on. Seconds later record.committed arrives naming the record and the entry's position, and the harness marks that entry committed. If an entry could not be committed, record.failed arrives instead, with the rule that was not met.
Set up an endpoint
Under Settings, Developer, Webhooks in the dashboard, add the URL PacSpace should post to. The URL must be https on a public host; a private address, a loopback name, or credentials in the URL are refused with "The endpoint must be an https URL on a public host."
Sandbox and Production each keep their own endpoint, signing secret, and deliveries; the Webhooks page shows the environment in view. One endpoint is active per environment, so disable the current one before adding or enabling another. The signing secret is shown once when the endpoint is made. Keep it where your handler can read it and nowhere else.
"Send a test event" posts a webhook.test delivery down the same path every real event takes, so you can see a signed delivery arrive before anything commits.
What arrives
Every delivery is an HTTP POST with a JSON body:
{
"event": "record.committed",
"timestamp": "2026-10-05T14:09:59.204Z",
"data": { "...": "the event's payload" }
}and these headers:
| Header | What it carries |
|---|---|
Content-Type | application/json |
X-Webhook-Event | The event name, the same as event in the body. |
X-Event-ID | An id for this event. The same event sent again carries the same id, so your handler can skip a repeat. |
X-PacSpace-Timestamp | When the delivery was signed, as Unix time in milliseconds. |
X-PacSpace-Signature | v1= and the signature over the timestamp and the body. See Signature verification. |
Answer with any 2xx within ten seconds. Anything else, or no answer, is a failed attempt.
Delivery and retries
Delivery is at least once. A failed attempt is tried again on a doubling schedule with a little jitter: 30 seconds, then 1, 2, 4, 8, 16, and 32 minutes, then about 1, 2, and 4 hours, then every 8 hours, until 72 hours have passed. That span covers a deploy, a weekend, or an incident on your side without the event being lost to a manual step. After the schedule is spent the delivery is marked failed and kept for 30 days, and the Webhooks page shows it; "Retry failed deliveries" puts every failed one back on a fresh 72-hour schedule once your endpoint is fixed. Each attempt, retries included, is signed when it is sent, so it passes the five-minute clock check.
Because delivery is at least once, your handler should be safe to run twice for the same X-Event-ID. For record.committed, acting twice is harmless when the action is "mark entry 3 of eval-run-4417 committed".
If an event spends its whole schedule and nothing at all has reached your endpoint since that event was queued, PacSpace disables the endpoint. The Webhooks page says so, and "Enable the endpoint, then retry the failed deliveries" is the whole recovery. An endpoint that took even one delivery in those 72 hours is left alone. While an endpoint is disabled, nothing is sent to it and new events are not queued for it; the history is the way to learn what committed in that time.
Rotating the secret
Rotate the signing secret from the Webhooks page. With overlap, the old secret keeps signing for 24 hours beside the new one: each delivery in that window carries two signatures in the header, comma separated, and your code passes if either matches the secret it holds. Move your code to the new secret inside the window. Without overlap, the old secret stops at once.
Events for a records workspace
| Event | When |
|---|---|
record.committed | An entry is in the record. Carries the record and the entry's position. |
record.failed | A queued entry could not be committed. Carries the code and a plain sentence. |
webhook.test | You pressed "Send a test event". |
See Event types and Payload reference.
The webhook and the record
The webhook tells you what happened to a write. The record itself is what records.history returns and what the Shared Record shows, and a missed webhook changes nothing about it. If a delivery never arrives, the history still shows the entry committed, or failed with its code.