Skip to content
PacSpace
Talk to us

Webhook endpoints

Register the endpoint that receives record.committed and record.failed, rotate its secret, send a test event, and read or retry deliveries, from the dashboard routes.

These are the routes behind Settings, Developer, Webhooks. What arrives at the endpoint, and how to check its signature, is on the Webhooks pages; this page is the endpoint's own lifecycle.

Every route here needs a dashboard session cookie (-b pacspace-dashboard-cookies.txt). A POST or DELETE made with the cookie also sends X-Pacspace-CSRF: 1 and Origin: https://app.pacspace.io; without them the answer is 403. Every POST and DELETE takes the admin or manager role; any member can read.

Sandbox and Production each keep their own endpoint, secret, and deliveries. Send X-Environment: sandbox or X-Environment: production to say which; with no header the routes act on Production.

Base URL: https://app.pacspace.io

http
POST https://app.pacspace.io/dashboard/webhooks

Register an endpoint

An evaluation team registers the endpoint its harness listens on, for Sandbox first:

bash
curl -X POST https://app.pacspace.io/dashboard/webhooks \
  -b pacspace-dashboard-cookies.txt \
  -H "X-Pacspace-CSRF: 1" \
  -H "Origin: https://app.pacspace.io" \
  -H "X-Environment: sandbox" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://evals.example.com/hooks/pacspace" }'

url is an https address on a public host, up to 255 characters. An address on a private network, on localhost, or with credentials in it is refused with 400: "The endpoint must be an https URL on a public host."

Answer 201 Created

json
{
  "success": true,
  "data": {
    "id": "cmw4m1p7a0003s601d2e9f5tk",
    "tenantId": "cmw3h8d2q0000s601x1v0r7kp",
    "creatorId": "cmw3h8d2q0001s601b4n6c2wd",
    "url": "https://evals.example.com/hooks/pacspace",
    "secret": "wh_live_...",
    "createdAt": "2026-10-05T13:55:03.512Z",
    "updatedAt": "2026-10-05T13:55:03.512Z",
    "disabledAt": null
  },
  "message": "webhook created successfully"
}

secret is shown once, here. It is what your handler checks X-PacSpace-Signature with; store it where your secrets live.

One endpoint is active per environment. Registering a second while one is enabled is refused with 409: "One active endpoint per tenant. Disable the current endpoint before adding or enabling another."

List endpoints

bash
curl "https://app.pacspace.io/dashboard/webhooks?page=1&limit=10" \
  -b pacspace-dashboard-cookies.txt \
  -H "X-Environment: sandbox"

limit is 10 by default and at most 100.

Answer 200 OK

json
{
  "success": true,
  "data": {
    "data": [
      {
        "id": "cmw4m1p7a0003s601d2e9f5tk",
        "url": "https://evals.example.com/hooks/pacspace",
        "disabledAt": null,
        "disabledReason": null,
        "createdAt": "2026-10-05T13:55:03.512Z",
        "updatedAt": "2026-10-05T13:55:03.512Z"
      }
    ],
    "pagination": { "page": 1, "limit": 10, "total": 1, "totalPages": 1 }
  },
  "message": "Webhooks retrieved successfully"
}

disabledReason is operator when a person disabled the endpoint and sustained_failure when PacSpace did, after an event spent its whole retry schedule with nothing delivered since it was queued.

GET /dashboard/webhooks/:id answers one endpoint: id, url, disabledAt, lastUsedAt, createdAt, and updatedAt. The secret is never in either answer.

Rotate the secret

bash
curl -X POST https://app.pacspace.io/dashboard/webhooks/cmw4m1p7a0003s601d2e9f5tk/rotate-secret \
  -b pacspace-dashboard-cookies.txt \
  -H "X-Pacspace-CSRF: 1" \
  -H "Origin: https://app.pacspace.io" \
  -H "X-Environment: sandbox" \
  -H "Content-Type: application/json" \
  -d '{ "overlap": true }'

overlap is required. With true, the previous secret keeps signing for 24 hours and every delivery in that window carries both signatures, so you can change the handler's secret without dropping a delivery. With false, the previous secret stops at once and previousSecretExpiresAt is null.

Answer 201 Created

json
{
  "success": true,
  "data": {
    "id": "cmw4m1p7a0003s601d2e9f5tk",
    "secret": "wh_live_...",
    "previousSecretExpiresAt": "2026-10-06T14:20:41.090Z"
  },
  "message": "Signing secret rotated."
}

Deliveries still waiting are signed when they are sent, so they carry the new secret. See Signature verification for reading two signatures.

Send a test event

bash
curl -X POST https://app.pacspace.io/dashboard/webhooks/cmw4m1p7a0003s601d2e9f5tk/test \
  -b pacspace-dashboard-cookies.txt \
  -H "X-Pacspace-CSRF: 1" \
  -H "Origin: https://app.pacspace.io" \
  -H "X-Environment: sandbox"

Queues a webhook.test event, signed like any other, and answers { "eventId": "evt_webhook_test_..." } with "Test event queued." Read GET /dashboard/webhooks/deliveries/:eventId to watch it land. A disabled endpoint refuses a test with 400: "The endpoint is disabled."

Disable or enable

bash
curl -X POST https://app.pacspace.io/dashboard/webhooks/cmw4m1p7a0003s601d2e9f5tk/toggle \
  -b pacspace-dashboard-cookies.txt \
  -H "X-Pacspace-CSRF: 1" \
  -H "Origin: https://app.pacspace.io" \
  -H "X-Environment: sandbox"

Each call flips the endpoint. The answer carries the endpoint with disabledAt and disabledReason set, or both null once enabled. Enabling is refused with 409 while another endpoint in the environment is enabled.

Nothing is sent to a disabled endpoint, and an event that arises while it is disabled is not queued for later: the history is the way to learn what committed in that time. Enabling the endpoint is your statement that the receiver is back; retry the failed deliveries after.

Deliveries

bash
curl "https://app.pacspace.io/dashboard/webhooks/deliveries?limit=20&offset=0" \
  -b pacspace-dashboard-cookies.txt \
  -H "X-Environment: sandbox"
ParameterValues
statuspending, delivered, or failed
limit, offsetPaging. limit is 20 by default and at most 100.
startDate, endDateDates as YYYY-MM-DD; they bound when the event was queued, and endDate takes in that whole day
sortBycreatedAt (the default), deliveredAt, or attempts
sortOrderdesc (the default) or asc

Answer 200 OK

json
{
  "success": true,
  "data": {
    "deliveries": [
      {
        "eventId": "evt_anc_01jb4r7w2k9x",
        "status": "delivered",
        "attempts": 1,
        "url": "https://evals.example.com/hooks/pacspace",
        "payload": { "event": "record.committed", "timestamp": "2026-10-05T14:09:59.204Z", "data": { "...": "..." } },
        "deliveredAt": "2026-10-05T14:10:00.031Z",
        "nextAttemptAt": "2026-10-05T14:09:59.204Z",
        "createdAt": "2026-10-05T14:09:59.204Z",
        "dataPurged": false
      }
    ],
    "pagination": { "total": 1, "limit": 20, "offset": 0 }
  }
}

failed means the 72-hour schedule is spent. A failed delivery is kept for 30 days. dataPurged is true once the delivery's payload has been removed under your retention window, and url and payload are then null.

GET /dashboard/webhooks/deliveries/:eventId answers one delivery: eventId, status, attempts, deliveredAt, nextAttemptAt, and createdAt.

GET /dashboard/webhooks/deliveries/summary answers the counts the Webhooks page shows: pending, failed, newestFailedQueuedAt, and autoDisabledAt, the moment PacSpace disabled the endpoint, or null.

GET /dashboard/webhooks/deliveries/export takes the same filters except paging and answers up to 10,000 rows as a CSV file, with the columns Event ID, Status, Attempts, URL, Delivered At, Created At, and Payload.

Retry

bash
curl -X POST https://app.pacspace.io/dashboard/webhooks/deliveries/retry-failed \
  -b pacspace-dashboard-cookies.txt \
  -H "X-Pacspace-CSRF: 1" \
  -H "Origin: https://app.pacspace.io" \
  -H "X-Environment: sandbox"

Every failed delivery goes back to pending, due now, with its attempt count reset and a fresh 72-hour schedule. The answer carries queued, the number put back. While every endpoint in the environment is disabled the retry is refused with 409: "The endpoint is disabled. Enable it, then retry the failed deliveries."

POST /dashboard/webhooks/deliveries/:eventId/retry retries one. A failed delivery goes back on a fresh schedule; a pending one is brought forward when its next attempt is more than two minutes away. A delivery that already reached the endpoint, or a pending one due within two minutes, is refused with 400.

Delete

bash
curl -X DELETE https://app.pacspace.io/dashboard/webhooks/cmw4m1p7a0003s601d2e9f5tk \
  -b pacspace-dashboard-cookies.txt \
  -H "X-Pacspace-CSRF: 1" \
  -H "Origin: https://app.pacspace.io" \
  -H "X-Environment: sandbox"

Answers data: null with "Webhook deleted successfully".

Routes

RouteMethodRoleWhat it does
/dashboard/webhooksPOSTadmin, managerRegister an endpoint
/dashboard/webhooksGETany memberList endpoints
/dashboard/webhooks/:idGETany memberOne endpoint
/dashboard/webhooks/:id/rotate-secretPOSTadmin, managerRotate the signing secret
/dashboard/webhooks/:id/testPOSTadmin, managerSend a test event
/dashboard/webhooks/:id/togglePOSTadmin, managerDisable or enable
/dashboard/webhooks/:idDELETEadmin, managerDelete
/dashboard/webhooks/deliveriesGETany memberDeliveries
/dashboard/webhooks/deliveries/summaryGETany memberPending and failed counts
/dashboard/webhooks/deliveries/exportGETany memberDeliveries as CSV
/dashboard/webhooks/deliveries/:eventIdGETany memberOne delivery
/dashboard/webhooks/deliveries/:eventId/retryPOSTadmin, managerRetry one
/dashboard/webhooks/deliveries/retry-failedPOSTadmin, managerRetry every failed delivery