app.webhook() lets you receive messages through HTTP POST requests instead of the app.messages stream. It handles two webhook formats through the same method:
| Native Spectrum webhook | Fusor webhook | |
|---|---|---|
| Body | HMAC-signed, normalized JSON | Protobuf envelope (raw provider request) |
| Auth | HMAC over body, verified with webhookSecret | Platform’s own signature via provider verify() |
| Requires a Fusor provider | No | Yes |
(space, message) pair either way.
Configuring a webhook secret
Native Spectrum webhooks require a signing secret for HMAC verification. Pass it toSpectrum():
webhookSecret option can also be supplied via the SPECTRUM_WEBHOOK_SECRET environment variable (the explicit option takes precedence). A native delivery that arrives without a configured secret is answered 500.
Receiving deliveries
Callapp.webhook() from your HTTP server’s POST route. The method has two overloads:
- Web Request (Hono / Bun.serve / Workers)
- Raw (Express / Node)
message.id for exactly-once side effects.
Framework adapters
First-party adapters mount the endpoint for you and handle raw-body parsing correctly. Each is an optional subpath import — install the framework as a peer dependency only if you use it.- Hono
- Express
- Elysia
| Option | Type | Default | Description |
|---|---|---|---|
app | Spectrum instance | — | The instance returned by await Spectrum({...}). |
onMessage | (space, message) => void | Promise<void> | — | Invoked once per inbound message, fire-and-forget. |
path | string | "/spectrum/webhook" | Route the endpoint is mounted on. |
What the SDK handles
- Signature verification. Native webhooks are verified with
HMAC-SHA256overv0:<timestamp>:<rawBody>, with a 5-minute replay window. Bad signature returns401, missing headers return400. - Payload deserialization. Native webhook JSON is deserialized into normal
Message/Spaceobjects, including reactions and grouped items. - Attachment rehydration. Native webhooks carry attachment metadata only.
read()andstream()fetch the bytes lazily via the platform. - Format detection. Native vs Fusor is detected per request by payload shape — JSON for native, protobuf for Fusor.
Delivery semantics
app.webhook() is stateless and request-scoped — it does not feed app.messages, and it never opens the streaming connection. Both formats deliver at-least-once, so dedupe on message.id for exactly-once side effects.
For more on Spectrum’s webhook delivery model, see the Webhooks documentation.