What is a webhook?
A webhook is the inverse of a normal API call. In a normal API call, your code reaches out and asks a service for data — you poll, you wait, you parse the response. In a webhook, the service reaches out to you: a message arrives, an event fires, and the servicePOSTs the details to a URL you’ve published. You’re not asking anymore; you’re being told.
For Spectrum, that means you write a regular HTTP handler — a function in your server that runs whenever a request comes in — and register its URL with us once. From then on, every inbound message across every enabled platform is delivered to that URL as a signed JSON POST. No long-lived process to babysit, no platform credentials in your runtime, no reconnect logic.
What’s in a delivery (and what isn’t)
The webhook tells you what happened and enough about it to decide what to do next — nothing more. It is not a vehicle for raw bytes, lazy-loaded SDK fields, or anything that requires a liveSpectrum() instance to materialize.
What every delivery carries:
- Routing context.
space.id(the conversation),message.id(the event),sender(who triggered it), and platform identifiers you can use to look things up in your own systems. - Content metadata. The
contentfield’stypediscriminator plus every JSON-safe field the SDK exposes — text, filenames, MIME types, sizes, URLs. - Provenance. Signed
X-Spectrum-Signature/X-Spectrum-Timestampheaders, plus anX-Spectrum-Webhook-Idheader for dedupe and replay protection.
- Bytes for binary content. Attachments, voice memos, and contact photos ship
mimeType/size/ filename — never the raw bytes themselves and never a download URL. Fetching the actual content is a separate step. - Resolved link previews. The
richlinkarm ships theurlonly. To render an OG-style title, summary, or cover image, fetch the URL yourself and parse the tags — the worker doesn’t do that on your behalf, both to keep delivery latency bounded and to avoid making the worker an attack surface for hostile URLs. - Recursive message trees. A reaction ships a slim reference to the message it targets (
id,platform,timestamp, an 80-charcontentPreview) — not the full target. Look it up in your own store usingtarget.id. See Events → Target refs for the exact shape.
How this guide flows
The next six pages build on each other. Skim straight to the topic you need, or read top-to-bottom in about fifteen minutes for the complete model.- Quickstart — register a URL, verify a signature, and receive your first real delivery in five minutes.
- Events — the exact wire format, every header and every field, with real examples.
- Verifying signatures — why the verifier looks the way it does, with copy-paste code in four languages.
- Delivery and retries — what happens when your endpoint is slow, down, or returns an unexpected status code.
- Managing webhooks — operate at scale: register, list, delete, and rotate signing secrets via the API or the dashboard.
- Troubleshooting — common symptoms, root causes, and fixes when something goes wrong.
Three ways to manage webhooks
Three surfaces expose the same three operations (register, list, delete):- Dashboard. UI under the Webhook tab of your workspace. No terminal or auth headers required — appropriate for non-technical teammates.
- API reference. OpenAPI schemas for
List webhooks,Register webhook, andDelete webhook. Mintlify also renders the page as a runnable browser playground for live testing. curlor any HTTP client. The terminal flow used in the Quickstart and the rest of this guide. Scriptable and CI-friendly — the right surface for code generation and automation.
When to use webhooks
Reach for webhooks when any of these are true:- Your service is HTTP-shaped already. A Node/Bun/Python/Go backend with an inbound endpoint is a one-line addition for webhooks; running a long-lived
Spectrum()loop is more friction. - You don’t want to host the SDK. No
Spectrum()process, no platform credentials in your runtime, no reconnect logic. - You want to fan out one message to multiple services. Register multiple webhook URLs per project — each one receives every event, independently.
- You’re integrating from a serverless platform (Vercel, Cloudflare Workers, Lambda) where a long-lived process isn’t an option.
spectrum-ts SDK loop when you need outbound message control inside the same process that consumes inbound, or when you’re iterating fast in dev without a deployable URL.
How it works
When a message arrives on any enabled platform for a project that has webhooks registered:- Our worker receives the message from the platform.
- It serializes the event to JSON.
- For every URL registered on that project, it computes an HMAC-SHA256 signature and POSTs the body.
- Your server verifies the signature, returns
2xx, and processes the event.
Your webhook URL must be a public HTTPS endpoint. The worker won’t deliver to plain
http://, to private/internal addresses (localhost, 10.x, cloud-metadata IPs), or through a redirect — see Delivery → Where we won’t deliver.The mental model
Four ideas cover everything else in these docs:| Concept | What it means |
|---|---|
| Per-project, per-URL | Webhooks are owned by a project. A project can have many URLs; each URL is independent. |
| Per-URL signing secret | Every webhook gets its own 64-character signing secret, returned exactly once at registration time. Different URLs, different secrets. |
| Every URL gets every event | There is no per-webhook event subscription today. You branch on the event field (or X-Spectrum-Event header) in your handler. |
| At-least-once delivery, in-order per project | A delivery may arrive twice on retry. Dedupe on webhookId + message.id. There is no Exactly-Once guarantee. |
Currently emitted events
Today there is one event:messages. Each delivery carries X-Spectrum-Event: messages and a body of shape { event, space, message } — see Events for every field.
Reactions aren’t a separate event — they already arrive today inside a messages payload, distinguished by message.content.type (you branch on content.type, not event). The set of top-level events may still grow later; new event types are additive, so handlers that ignore unknown values keep working without changes.
Security in one paragraph
Each delivery includes anX-Spectrum-Signature header containing an HMAC-SHA256 of the request body, keyed by your per-webhook signing secret. Anyone can POST to your URL — only Spectrum can compute a signature that verifies. Recompute it on your side and reject anything that doesn’t match. The full walkthrough, with copy-pasteable code in four languages, is on Verifying signatures.
Where to next
Quickstart
Wire up a URL, verify a signature, and receive a real message end-to-end in roughly five minutes.
How it works
The mechanics of delivery — useful as a mental model before reading the implementation pages.