You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs-src/webhooks/events.mdx.vel
+66-15Lines changed: 66 additions & 15 deletions
Original file line number
Diff line number
Diff line change
@@ -3,8 +3,22 @@ title: Events
3
3
description: The exact wire format Spectrum sends — headers, body, and what each field contains
4
4
---
5
5
6
+
import { TypeTooltip } from "/snippets/type-tooltip.mdx";
7
+
8
+
{% set space = symbol("ts:spectrum-ts#Space") %}
9
+
{% set user = symbol("ts:spectrum-ts#User") %}
10
+
11
+
{# Note: We intentionally do NOT use TypeTooltip for `Content` or
12
+
`InboundMessage` because:
13
+
- `Content = z.infer<typeof contentSchema>` resolves to a Zod inference
14
+
reference that's unhelpful to customers.
15
+
- `InboundMessage` is generic and the extractor returns an empty signature.
16
+
The Space and User types are plain interfaces that extract cleanly. #}
17
+
6
18
This page is the spec. Every webhook delivery is an HTTPS `POST` with a JSON body and four custom headers. The shape below is what your handler will see on every request.
7
19
20
+
The body's `space`, `message`, `sender`, and `content` fields are the same shapes you'd see from the [`spectrum-ts` SDK](/spectrum-ts/messages) — with one important difference: function-typed properties (`.read()`, `.stream()`, `.reply()`, `.react()`, etc.) are stripped before serialization, since functions can't survive `JSON.stringify`.
The exact ID formats and the `platform` value are decided by each provider — not by Spectrum. The example above is real prod output for an iMessage delivery; WhatsApp Business and any future platform will use their own conventions. Don't pattern-match on these strings; use them as opaque identifiers.
62
+
47
63
## Headers
48
64
49
65
| Header | Value | Notes |
@@ -76,8 +92,8 @@ This is the only event currently emitted. It fires once per inbound message that
76
92
| Field | Type | Description |
77
93
| --- | --- | --- |
78
94
| `event` | `"messages"` | Discriminator. Always `"messages"` for this payload. |
79
-
|`space`|object | The conversation context. See [Space](#space). |
80
-
|`message`| object | The inbound message. See [Message](#message). |
95
+
| `space` | <TypeTooltip name="Space" type={`{{ space.signature }}`} /> | The conversation context. See [Space](#space) below. |
96
+
| `message` | object | The inbound message. See [Message](#message) below. |
81
97
82
98
#### Space
83
99
@@ -86,19 +102,19 @@ This is the only event currently emitted. It fires once per inbound message that
86
102
| `id` | `string` | Opaque, stable identifier for the conversation. Format varies by platform and space type — treat it as a string you store and pass back unchanged. For iMessage DMs, looks like `any;-;+<E.164>`; for groups, a chat GUID. |
87
103
| `platform` | `string` | The platform that owns this space. See [Providers](/spectrum-ts/providers) for the current set of values; new platforms add new values without breaking existing payloads. |
88
104
89
-
The `space.id` matches the `space.id` you'd see from the [`spectrum-ts` SDK](/spectrum-ts/spaces-and-users). To send a reply, pass it to `space.send(...)` from a separately-running SDK instance — there is no public HTTP send-message endpoint today.
105
+
The `space.id` matches the `space.id` you'd see from the [`spectrum-ts` SDK](/spectrum-ts/spaces-and-users). To send a reply, pass it to <TypeTooltip name="Space" type={`{{ space.signature }}`} />`.send(...)` from a separately-running SDK instance — there is no public HTTP send-message endpoint today.
90
106
91
107
#### Message
92
108
93
109
| Field | Type | Description |
94
110
| --- | --- | --- |
95
-
|`id`|`string`| Stable, platform-prefixed message id. Use this for idempotency. |
96
-
|`platform`|`string`| The platform that sourced the message. |
111
+
| `id` | `string` | Stable opaque identifier for the message. The current format is `spc-msg-<uuid>` but treat it as opaque — use it for idempotency, don't parse it. |
112
+
| `platform` | `string` | The platform that sourced the message. Same value as `space.platform`. |
97
113
| `direction` | `"inbound"` | Always `"inbound"` — outbound messages are not delivered as webhooks. |
98
114
| `timestamp` | `string` | ISO 8601 UTC timestamp from the platform (when the user sent it). |
99
-
|`sender`|object | The user who sent the message — `{ id, platform }`. |
100
-
|`space`|object| A copy of the top-level `space` field, denormalized for convenience. |
101
-
|`content`| object | The message content. Shape depends on the message type — see below. |
115
+
| `sender` | <TypeTooltip name="User" type={`{{ user.signature }}`} /> | The user who sent the message — `{ id, platform }`. The `id` format is platform-defined (e.g. for iMessage it's the E.164 phone number `+15551234567`; for WhatsApp Business it's the WA contact id). |
116
+
| `space` | <TypeTooltip name="Space" type={`{{ space.signature }}`} /> | A copy of the top-level `space` field, denormalized for convenience. |
117
+
| `content` | object | The message content. Shape depends on the message type — see [Content shapes](#content-shapes) below. |
name: string; // original filename, e.g. "IMG_4127.HEIC"
148
+
mimeType: string; // e.g. "image/heic", "audio/mp4", "application/pdf"
149
+
size: number; // bytes
150
+
};
151
+
// future content types may be added; handle unknown `type` values defensively
132
152
```
133
153
134
-
The complete reference (with every content type and its fields) lives in [Spectrum content types](/spectrum-ts/content). The same definitions apply on the wire.
154
+
**Text** is what you'll see for the vast majority of inbound messages.
155
+
156
+
**Attachment** is what you'll see for any non-text content from iMessage — photos, voice memos, audio files, videos, documents. The `mimeType` field is the discriminator for *what kind* of attachment it is:
| `video/*` | Video clip | `video/mp4`, `video/quicktime` |
163
+
| `application/*` | Document or file | `application/pdf`, `application/zip` |
164
+
165
+
The SDK's `Content` type defines additional arms (`reaction`, `richlink`, `poll`, `contact`, etc.) that may appear in future webhook deliveries. See [Spectrum content types](/spectrum-ts/messages#narrowing-content) for the canonical list. If a new arm ships, your `default:` switch arm is what catches it gracefully — see the snippet below.
166
+
167
+
<Warning>
168
+
**Attachment payloads are metadata only.** The wire format does not include the file bytes or a download URL — only the filename, MIME type, and size. To process the actual content you'll need an additional retrieval step. A first-class HTTP download endpoint is on the roadmap; until then, contact support if you need attachment retrieval.
169
+
</Warning>
135
170
136
171
<Tip>
137
172
Always handle unknown `content.type` values gracefully — new content types may be added without a breaking version bump. A `default:` arm in your switch that logs and moves on is enough.
173
+
174
+
```ts
175
+
switch (content.type) {
176
+
case 'text':
177
+
handleText(content.text);
178
+
break;
179
+
case 'attachment':
180
+
if (content.mimeType.startsWith('image/')) handleImage(content);
181
+
else if (content.mimeType.startsWith('audio/')) handleAudio(content);
0 commit comments