Overview
Overview
OpenPoly webhooks notify operator backend after business state already committed.
Delivery model
- OpenPoly business flow commits DB state.
- OpenPoly writes outbox event.
- Dispatcher creates per-endpoint delivery records for eligible active subscriptions, or records a terminal skip observation when none are eligible.
- Dispatcher signs raw JSON payload and sends
POSTrequest. - Operator receiver verifies signature, deduplicates
event_id, then processes event.
Webhook send is not part of a wallet mutation or trade/ticket transaction. Delivery, retry, and replay never authorize another debit, credit, or reversal.
Opinion and Polyflight use the same signed snake_case envelope and event_id idempotency boundary. Polyflight's data is camelCase; stakeMinor, feeMinor, netStakeMinor, and potentialPayoutMinor are the immutable purchase snapshot, while amountMinor on payout/refund events describes the owed or confirmed credit according to event type and status. A full refund or debit compensation reverses the stake and is not another fee.
Important guarantees
- business state does not roll back if webhook delivery fails
- same outbox event can produce multiple delivery attempts
- replay creates another delivery attempt for same event
- disabled endpoint receives no new deliveries
- retained dead-letter deliveries remain reviewable/resendable while their endpoint history exists; deleting the endpoint removes those delivery records
What receiver must do
- read raw request body
- verify
X-Polynion-Signature - reject stale timestamp
- dedupe by
event_idin the same durable transaction as business handling - return
2xxonly after durable processing, including when theevent_idwas already committed
Never sign or verify a dashboard JSON preview: it is reconstructed event data, not the original signed request bytes. The receiver must verify the raw bytes actually received.
Replay preserves event identity and original occurrence time, not byte-identical HTTP. Opinion order callbacks reconstruct current durable order/trade data at send time; Polyflight callbacks use their stored event snapshots. Store the actual received raw body if original transport evidence is required, and continue deduplicating by event_id even if a later delivery's reconstructed fields differ.
State model
Outbox event statuses:
pendingdispatchingdispatchedfaileddead_letter
Delivery statuses:
pendingsentfaileddead_letter
Event-only monitor rows
The delivery monitor also shows real outbox events with no retained delivery record. These rows have no delivery ID, endpoint, HTTP status or receiver response; their retained delivery attempt count is zero. Pending/dispatching events display pending; failed/dead-letter events preserve those states. Terminal dispatched event-only rows can be explicitly backfilled using Resend, which creates real pending deliveries for currently active, subscribed endpoints.
New dispatcher observations distinguish no_endpoint (zero configured endpoints), endpoint_disabled (configured but none active), and not_subscribed (active endpoints but no matching subscription). These do not retry automatically; configuring an endpoint later does not schedule historical callbacks without an explicit resend.
For older terminal dispatched events without retained delivery rows, history_unavailable means exactly that: delivery history is missing. It does not prove that no endpoint existed or that a callback was never sent or received. Endpoint deletion can remove delivery records. Historical baseline recording preserves original event timestamps and business data; its disposition_recorded_at is the later recording time, not a guessed historical attempt or endpoint-change time.
The monitor defaults date filtering to event occurrence time. Choosing delivery time excludes event-only rows. Its filter-consistent summary and CSV export cover the same applied filters; CSV exports all matches up to 5000 rows and returns 422 EXPORT_TOO_LARGE above that cap rather than truncating. Reconstructed callback data includes available monetary/business identifiers, not signed request bytes. Exports omit secrets and receiver response samples but still contain sensitive operator data. See the list and export contract.
Three IDs serve different purposes: public string event.event_id is the receiver dedupe key; numeric outbox event.id selects an event-only historical resend; numeric delivery id selects a retained delivery. replayable=false on a terminal event-only row does not rule out historical Resend. Current endpoint settings and saved setup history explain configuration changes but cannot reconstruct missing HTTP responses.
Delivery boundaries
2xx-> delivery markedsent410-> delivery markeddead_letterimmediately- other non-
2xx-> retry until max attempts, thendead_letter - timeout/network/DNS/TLS failure -> retry until max attempts, then
dead_letter
Where operators configure webhooks
- create and edit subscriptions (all ten Opinion and eight Polyflight event types):
/docs/operator-api/webhook-endpoints - inspect Opinion/Polyflight deliveries, exact event amounts, and replay:
/docs/operator-api/webhook-deliveries - verify signatures:
/docs/webhooks/signatures
Endpoint setup history
The Operator Dashboard's Callback Settings → Setup History tab shows the operator's endpoint configuration audit trail, separately from delivery attempts. It includes endpoints that have since been deleted. Each record shows the UTC recording timestamp, action, endpoint ID, before/after URL, authenticated actor, and expandable settings comparison. URLs are displayed as text, not navigation links.
Read history through the API
GET /api/operator/webhooks/endpoints/history?page=1&page_size=10
Authorization: Bearer <operator-api-key>
The endpoint requires webhooks:read (or *) and is scoped to the authenticated operator. It uses the standard page / page_size pagination parameters and returns newest records first, with record ID as the tie-breaker for equal timestamps:
{
items: Array<{
id: number
endpoint_id: number
action: 'baseline' | 'created' | 'updated' | 'deleted'
actor: {
type: 'staff' | 'api_key' | 'system'
id: number | null
label: string
}
before: EndpointSnapshot | null
after: EndpointSnapshot | null
secret_changed: boolean
created_at: string
}>
pagination: { page: number; page_size: number; total: number; total_pages: number }
}
created_at on the history record is the UTC time that change or baseline was recorded. EndpointSnapshot contains only url, status (active or disabled), enabled_events, max_attempts, timeout_seconds, has_secret, and the endpoint's own created_at / updated_at timestamps. An empty enabled_events array means all current and future event types. A null snapshot means the endpoint did not exist on that side: creation has no before, deletion has no after, and baseline capture has no reconstructed before.
Successful endpoint creation, configuration updates, enable/disable, secret replacement, and deletion record their audit entry atomically with the endpoint mutation. A failed write does not create a successful-change record.
Actor provenance and secret safety
- Staff writes record the authenticated staff account ID and its display name/email label.
- API-key writes record the authenticated key ID and key name. This identifies the credential used, not a guessed human user.
- Actor fields are derived by the server from authentication, never accepted from the mutation request body.
has_secretdescribes whether a secret is configured.secret_changedis a boolean indicating an explicit secret rotation or replacement during an update; initial endpoint creation recordsfalse. It does not reveal the secret or prove anything about earlier rotations.- History snapshots and responses contain no secret values, secret hashes, or encrypted secrets. The history view only displays boolean secret metadata.
Baseline limits
When tracking was enabled, existing endpoints were captured as baseline records with a system actor, null actor ID, and an explicitly unknown prior actor. A baseline is a snapshot of settings at capture time, not a reconstructed creation or update event. Earlier changes and the people or keys responsible cannot be recovered from it.
The baseline history record's created_at is the capture timestamp. The endpoint's original creation and last-update timestamps are preserved inside the snapshot and shown separately. They must not be presented as audited events or attributed to the baseline actor. A false secret_changed on a baseline does not mean the secret was never rotated before capture.
Deletion and retention
Deleting an endpoint retains its setup history, including the deletion record and earlier settings, so operators can still inspect who changed it and what URL or settings were configured. Deletion still removes that endpoint's delivery history; setup history does not retain delivery attempts, recreate them, or make them replayable. The dashboard deletion confirmation distinguishes these two histories.
