Skip to main content

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 service POSTs 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.
const app = await Spectrum({ projectId, projectSecret, providers: [imessage.config()] });
// Without webhooks, you'd run this loop yourself, forever.
for await (const [space, message] of app.messages) { /* ... */ }
# With webhooks, Spectrum runs the loop and POSTs each message to you.
POST https://your-app.com/spectrum-webhook
X-Spectrum-Event: messages
X-Spectrum-Signature: v0=<hmac>
X-Spectrum-Timestamp: 1747242392

{"event":"messages","space":{...},"message":{...}}
Spectrum handles staying connected to every supported platform, batching, reconnects, and signing. You handle the HTTP request that lands at your door.

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 live Spectrum() 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 content field’s type discriminator plus every JSON-safe field the SDK exposes — text, filenames, MIME types, sizes, URLs.
  • Provenance. Signed X-Spectrum-Signature / X-Spectrum-Timestamp headers, plus an X-Spectrum-Webhook-Id header for dedupe and replay protection.
What every delivery deliberately omits:
  • 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 richlink arm ships the url only. 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-char contentPreview) — not the full target. Look it up in your own store using target.id. See Events → Target refs for the exact shape.
This is the same shape Twilio, Stripe, and most other webhook providers use: the webhook is the doorbell, not the package. Use it to know an event happened and to route into your own handling; pull heavyweight payloads on demand when you actually need them.

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.
  1. Quickstart — register a URL, verify a signature, and receive your first real delivery in five minutes.
  2. Events — the exact wire format, every header and every field, with real examples.
  3. Verifying signatures — why the verifier looks the way it does, with copy-paste code in four languages.
  4. Delivery and retries — what happens when your endpoint is slow, down, or returns an unexpected status code.
  5. Managing webhooks — operate at scale: register, list, delete, and rotate signing secrets via the API or the dashboard.
  6. 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, and Delete webhook. Mintlify also renders the page as a runnable browser playground for live testing.
  • curl or 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.
Stick with the 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:
  1. Our worker receives the message from the platform.
  2. It serializes the event to JSON.
  3. For every URL registered on that project, it computes an HMAC-SHA256 signature and POSTs the body.
  4. Your server verifies the signature, returns 2xx, and processes the event.
Failed deliveries are retried with exponential backoff and jitter — up to 6 attempts spanning tens of seconds of backoff (longer if your endpoint hangs) — then dropped. There is no persistent retry queue or dead-letter destination — see Delivery and retries for the precise policy.
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:
ConceptWhat it means
Per-project, per-URLWebhooks are owned by a project. A project can have many URLs; each URL is independent.
Per-URL signing secretEvery webhook gets its own 64-character signing secret, returned exactly once at registration time. Different URLs, different secrets.
Every URL gets every eventThere 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 projectA 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 an X-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.
Your signing secret is returned exactly once, in the response of POST /webhooks/. There is no “show me my secret” endpoint. Store it in your secret manager immediately. If you lose it, delete the webhook and re-register the URL — you’ll get a new id and a new secret.

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.