PM Webhooks
Webhooks in both directions for Mission Control boards. Outbound: subscriptions POST matching board events to callback URLs with an HMAC-SHA256 signature, retried, health-tracked, auto-disabled and dead-lettered. Inbound: a public per-board URL receives external POSTs, with an optional HMAC signature to prove the sender, and folds each one into a board event.
What it is
PM Webhooks connect PM boards to external systems in both directions.
Outbound subscriptions: external systems register a callback URL, and when board events fire (from the sync/diff pipeline) matching events are POSTed there with an HMAC-SHA256 signature, retried, health-tracked, auto-disabled on repeated failure, and dead-lettered.
Inbound receiver: each inbound webhook owns a generated token URL that external systems POST to, and the payload is folded into a board event. Signing is opt-in, and recommended for anything reaching Omniscio from off this machine.
Where to find it
There is no screen for this. It is an agent and API surface: subscriptions are created, listed and removed through the app's local control server using the routes listed under For agents, and there is no settings panel to configure them from.
How it behaves
Key behaviors
- Signing: every delivery carries
X-AMC-Signature(HMAC-SHA256 of the raw payload using the webhook's secret) - Retry: 3 attempts with full-jitter backoff (base 2s, max 16s), 10s timeout per attempt
- Auto-disable: after 3 consecutive failures the webhook is marked inactive and an inbox alert surfaces
- Health reset: a single successful delivery resets the failure counter
- Event filtering: an empty
event_filterarray means all event types - Soft delete:
DELETEmarksis_deleted = 1, never destroys data
Dead-letter queue
Failed deliveries (all 3 retries exhausted) are queued in pm_webhook_dlq. A background service
ticks every 15 minutes:
- Purges entries past 10 retries or older than 14 days (anti-abandonment backstop), raising an operator alert when events are discarded
- Re-delivers retryable entries preserving the original
deliveryIdfor receiver idempotency, refreshing onlydeliveredAtand the signature - DLQ success does NOT reset the webhook's failure counter -- only live delivery success does
Settings
No dedicated settings. Requires Mission Control to be enabled.
Inbound webhooks (receiving events from other systems)
The other direction: instead of Omniscio pushing events out, an external system POSTs to a URL and Omniscio folds the payload into a board event that board automations can trigger on.
The URL is the credential. Each inbound webhook gets a generated token, and the endpoint is
POST /pm/webhooks/inbound/<token>. It carries no bearer auth and is reachable from the network,
so treat the full URL as a secret: anyone holding it can deliver events.
Signing (opt-in, recommended for anything off this machine)
Create the webhook with enableHmac: true and it gets an HMAC secret alongside its token. Every
delivery must then carry:
X-Webhook-Signature: sha256=<lowercase hex digest>
where the digest is HMAC-SHA256(secret, rawBody) -- the raw body bytes exactly as you sent
them, computed before any JSON parsing. Do not re-serialize a parsed object to sign it: pretty
printing, a space after a colon, or a different key order all change the bytes, and the signature
will not match. This is the same convention as GitHub and Stripe, and the same rule
outbound-webhooks.md gives for verifying Omniscio's own deliveries. The
comparison is constant-time.
Response codes
| Status | Meaning |
|---|---|
200 |
Accepted -- the body was folded into a board event |
400 |
Missing token, or the body is not a bounded JSON object |
401 |
Signature missing, malformed, or wrong |
404 |
Token unknown or inactive (or Mission Control is disabled) |
413 |
Body over 64 KiB |
429 |
Per-board rate limit (60 deliveries per minute) |
503 |
The webhook needs a signature but its secret could not be read |
401 and 503 are different failures and you must treat them differently. A 401 means your
signature is wrong and retrying unchanged will fail again. A 503 means the receiver temporarily
cannot verify anyone's signature -- your signature may be perfectly correct -- so retry rather than
alerting on a bad secret.
Duplicate deliveries
Webhooks get redelivered, so the receiver collapses a repeat rather than running the same automation twice. What counts as a repeat depends on what you send:
- Send a delivery id and the collapse is exact. Stamp one of
X-Idempotency-Key,X-Event-Id,X-Webhook-IdorX-Request-Idwith a value that is stable across retries of the same event and different for distinct events. A redelivery then collapses, and two different events never do -- even when their payloads are byte-identical. - Send nothing and the receiver falls back to a hash of the body. Two deliveries carrying the same body are then treated as the same event and collapse.
Either way the later delivery is answered 200 { "ok": true, "deduplicated": true } and is not
dispatched again.
This matters if you can legitimately post the same payload twice -- a monitor re-firing the same
alert, a poller re-posting an unchanged record. Without a delivery id the second one is
indistinguishable from a retry of the first, so it is dropped and folded into the earlier event.
Send a delivery id whenever a repeat is meaningful. A suppressed delivery is written to the server
log; nothing beyond the deduplicated flag reaches the sender.
For agents
Outbound subscription routes:
POST /pm/webhooks-- create a subscription ({ boardId, callbackUrl, events[], secret })GET /pm/webhooks-- list subscriptionsDELETE /pm/webhooks/:id-- soft-delete a subscription
Inbound receiver routes:
POST /pm/webhooks/inbound-- create an inbound webhook ({ boardId, name?, enableHmac? }); the response carries the token, and the HMAC secret whenenableHmacis setGET /pm/webhooks/inbound?boardId=<id>-- list inbound webhooks for a boardDELETE /pm/webhooks/inbound/:id-- soft-delete onePOST /pm/webhooks/inbound/:id/regenerate-- issue a new token (the old URL stops working)
The delivery endpoint itself, POST /pm/webhooks/inbound/:token, is public and takes no bearer
token.
Requires the missionControlEnabled setting.
Related
These are board-scoped subscriptions, which makes mission-control.md -- the parent page -- the natural next read. Do not confuse them with outbound-webhooks.md, Omniscio's own outbound webhook system, which is a separate mechanism with its own page. The full behavioural contract is recorded in pm-webhooks-contract.md, and every other page in this library is listed in INDEX.md.
Last verified 2026-10-01