Webhooks
Predictefy can POST a signed event to your HTTPS endpoint when a background job finishes or an execution changes status, so you do not have to poll for either.
Endpoint management is API-only today — there is no console screen for it yet.
Events
Section titled “Events”| Event | Scope | Fires when |
|---|---|---|
ingest.run.completed |
Platform-wide | the catalog ingest job finishes a run |
matcher.run.completed |
Platform-wide | the cross-match job finishes a run |
execution.status.changed |
Your account | a submitted execution, cancel, or refresh commits a different status |
The two job events carry that job’s run_log row — job, started_at, finished_at, ok, and a
counts tally. On a failed run ok is false and counts.error holds the message.
execution.status.changed carries lifecycle metadata only:
{ "executionId": "8a4fdf19-49e1-4702-9cb8-31fd9c46f7e1", "venue": "hyperliquid", "status": "filled", "previousStatus": "acked", "occurredAt": "2026-08-01T02:03:04.000Z"}It is tenant-scoped: it reaches only endpoints owned by the execution’s account. Your account id is routing context and is deliberately not in the payload.
It fires only on a real transition. An unchanged result, a lost concurrent update, or an already-terminal row emits nothing — so a quiet period means nothing changed, not that a delivery was dropped.
Registering an endpoint
Section titled “Registering an endpoint”All routes are API-key authenticated and scoped to your account.
curl -s "$PREDICTEFY_API_URL/v1/webhooks" \ -H "Authorization: Bearer pk_live_YOUR_KEY" \ -H "content-type: application/json" \ -d '{"url":"https://example.com/hooks/predictefy","events":["execution.status.changed"]}'The response includes a secret shown exactly once. Store it immediately; it is never returned
again, and listing your endpoints redacts it.
url must be public HTTPS. Localhost, loopback, and private or link-local addresses are
rejected — IPv4, IPv6, and IPv4-mapped-IPv6 literals alike. events must be a non-empty subset of
the table above.
GET /v1/webhooks— your endpoints, secrets redacted.DELETE /v1/webhooks/{id}— delete one, along with its queued deliveries. An id that is not yours returns404.GET /v1/webhooks/{id}/deliveries?after=<cursor>&limit=<n>— delivery rows withid,event,payload,status,createdAt, anddeliveredAt.limitdefaults to 50, capped at 200. Rows are always oldest-to-newest; passnextCursorback asafter. Both SDKs expose this asclient.webhooks.deliveries(id, params).
For local development, predictefy webhooks listen [--endpoint <id>] polls that route every two
seconds. Without an endpoint it creates a temporary non-routable one and deletes it on exit.
--forward http://localhost:PORT/path re-POSTs each payload locally.
What a delivery looks like
Section titled “What a delivery looks like”Each delivery is a POST to your URL with these headers:
| Header | Value |
|---|---|
Content-Type |
application/json |
X-Predictefy-Event |
the event name |
X-Predictefy-Timestamp |
Unix seconds when the delivery was signed |
X-Predictefy-Signature |
hex HMAC-SHA256 — see below |
Redirects are not followed, and each attempt times out after 10 seconds.
Verifying the signature
Section titled “Verifying the signature”The signature is a hex HMAC-SHA256 over `${timestamp}.${rawBody}` keyed by your endpoint
secret. Recompute it over the exact bytes you received — not a re-serialized object — and compare
in constant time:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody: string, headers: Record<string, string>, secret: string): boolean { const timestamp = headers['x-predictefy-timestamp']; const signature = headers['x-predictefy-signature']; if (!timestamp || !signature) return false; const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex'); const a = Buffer.from(signature, 'hex'); const b = Buffer.from(expected, 'hex'); return a.length === b.length && timingSafeEqual(a, b);}Optionally reject deliveries whose X-Predictefy-Timestamp is far from your clock, to blunt replay
attacks.
Retries
Section titled “Retries”Respond 2xx to acknowledge. Any non-2xx, network error, or timeout is retried with exponential
backoff, then abandoned:
| Attempt | Retry after |
|---|---|
| 1 → 2 | 1 minute |
| 2 → 3 | 5 minutes |
| 3 → 4 | 30 minutes |
| 4 → 5 | 2 hours |
| after 5 failed attempts | marked failed (no further retries) |
Make your handler idempotent. For job events, dedupe on the payload’s run_log identity; for
execution events, on executionId, status, and occurredAt.
Availability
Section titled “Availability”Delivery requires EXEC_WEBHOOKS_ENABLE=true on the execution service, which defaults to false.
Endpoint management is gated by READS_ENABLE_WEBHOOK_ROUTES. Register an endpoint and check
GET /v1/webhooks/{id}/deliveries to confirm both are live on the deployment you are calling.