Webhooks (receive HTTP POSTs into your inbox)
Webhooks run a local HTTP server that accepts incoming POST requests and turns each one into a row in the unified inbox, alongside Gmail, SMS, Slack, Telegram and RSS. Every source has its own URL path and bearer token, the channel is receive-only, and the page covers enabling it, adding and testing a source, the full set of response codes, and duplicate suppression.
What it is
Omniscio's webhook integration runs a local HTTP server that accepts incoming POST requests and surfaces each one as a row in the unified inbox — alongside Gmail, SMS, Slack, Telegram, and RSS. Each configured webhook source has a unique URL path and bearer token; every POST to that path becomes a "message" in that source's "conversation". This is a receive-only channel — there's no replying. Typical use cases: GitHub push/issue notifications, Stripe payment events, Zapier/Make pipelines, custom scripts that want to ping you, or any third-party service that supports outgoing webhooks.
Where to find it
Settings → Webhooks carries the master toggle and the port the local server listens on, and each source you add there produces the URL and bearer token you hand to the sender. What arrives lands in the inbox as its own conversation, and the same payloads are visible to the automation engine and to the app-wide search.
How it behaves
How to use it
- Enable webhooks. Settings → Webhooks → toggle
webhooksEnabledon. Pick the port the local HTTP server listens on (configurable viawebhookPortin config-store). - Add a webhook source. Each source gets a unique
sourceId(URL slug) and a bearer token Omniscio auto-generates. Copy the resulting webhook URL —http://<your-host>:<port>/hooks/<sourceId>— into whatever third-party service is going to POST to it. The/hooks/path segment is required: the server only answersPOST /hooks/:sourceId, and any path without it returns a hard404 Not found. - Test it. From a terminal,
curl -X POST -H "Authorization: Bearer <token>" -d '{"hello":"world"}' http://localhost:<port>/hooks/<sourceId>. Within a second the request should appear as a new row in the Omniscio inbox under that source's conversation. - Read in the inbox. Click a webhook row to see the full conversation panel — every POST received shows the request method, headers, formatted JSON body (auto-pretty-printed), receive timestamp, and remote IP.
- Pair with automations. Webhook payloads flow through the standard Automation engine — keyword-match the body, route to email forward, summarize in the daily digest, or trigger a Claude session to react to the event.
For agents
How it works
The HTTP server is /src/main/services/webhook-server.ts — built on Node's stdlib http.createServer (no Express dependency) with a 1 MB max body size and a 10-second body-read timeout to prevent slow-loris attacks. Source validation is dependency-injected via setSourceValidator() so the server doesn't import DB queries directly: each incoming request looks up its sourceId, refuses with 404 (Source not found) if missing, then verifies the Authorization: Bearer <token> header matches the stored token (a missing or mismatched token is a 401 Unauthorized). Successful POSTs build a WebhookPayload (sourceId, method, headers, body, receivedAt ISO timestamp, remoteAddress) and fan out to listeners registered with onWebhookReceived(). The payload also carries an optional bodyBase64 field alongside body when the raw bytes aren't clean UTF-8, so a binary or mis-encoded payload round-trips losslessly. Duplicate suppression: if a redelivered event carries a delivery-id header (x-github-delivery, idempotency-key, x-idempotency-key, x-hook-id, x-webhook-id, x-event-id, or x-request-id) already seen within the last 10 minutes, the server acks it 200 {ok: true, received: true, duplicate: true} but does not fan out to listeners or create a second inbox row — a sender's at-least-once retries never double-post. A POST with no delivery-id header always fans out (at-least-once delivery — your listeners must be idempotent). The unified-inbox bridge is /src/main/services/channels/webhook-adapter.ts, which translates each payload into a UnifiedMessage whose body is pretty-printed JSON when parseable (raw text otherwise). The adapter declares canSend: false so reply UI is hidden. Persistence: /src/main/db/queries-webhook.ts. Settings flag: webhooksEnabled in /src/shared/types.ts; the virtual-project constant WEBHOOK_PROJECT_ID = '__webhook__' lets the inbox group webhook items together. Search integration: SearchChannelType in types.ts includes 'webhook'. Response codes a sender should expect: 200 on success; 404 Not found for a path missing the /hooks/ segment; 404 Source not found for an unknown sourceId; 405 (with an Allow: POST header) for a non-POST method; 401 Unauthorized for a missing/mismatched bearer; 403 for a browser cross-site (Sec-Fetch-Site: cross-site) request; 429 Rate limit exceeded (with a Retry-After header) once a single source exceeds 60 authenticated requests per 60 seconds — a busy sender WILL hit this and must honor Retry-After and retry, not drop the event; 413 Payload too large for a body over the 1 MB cap; 408 Request timeout if the body read stalls past the 10-second slow-loris timeout; and 500 in two distinct cases: Source validator not configured when the server isn't fully wired, or Internal server error when an unexpected error occurs while reading the body. The 413 Payload too large, 408 Request timeout, and the internal-error 500 (Internal server error) responses additionally include a requestId in their body you can quote when reporting a failed delivery; the other error codes — including the config-error 500 (Source validator not configured) — do not. Every response body — success or error — also carries a boolean ok field (true for a 2xx status, false otherwise) as its leading discriminator, e.g. a successful post acks 200 {ok: true, ...}. Separately, the same server answers GET /health → 200 {ok: true, status:'ok'} when fully wired, or 503 {ok: false, status:'unavailable'} when the source validator (or listener) isn't up.
Related
- INDEX.md — full library index
- rss-integration.md — sibling read-only channel for pull-based feeds
- automations-and-auto-replies.md — keyword rules that route webhook payloads
- daily-digest.md — webhook events can roll up into the once-a-day briefing
- global-search.md — Ctrl+K searches webhook bodies alongside every other channel
Last verified 2026-09-23