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
|`platform`|`string`| The platform that owns this space (`imessage`, `whatsapp_business`). |
86
+
|`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
+
|`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
88
89
-
The `space.id` matches the `space.id` you'd see from the [`spectrum-ts` SDK](/spectrum-ts/spaces-and-users). You can use it to send a reply back via the [Spectrum API](/api-reference/introduction).
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.
90
90
91
91
#### Message
92
92
@@ -100,6 +100,25 @@ The `space.id` matches the `space.id` you'd see from the [`spectrum-ts` SDK](/sp
100
100
|`space`| object | A copy of the top-level `space` field, denormalized for convenience. |
101
101
|`content`| object | The message content. Shape depends on the message type — see below. |
102
102
103
+
#### Idempotency: the `message.id` rule
104
+
105
+
A single inbound message **always carries the same `message.id` across every delivery it produces**, no matter how many webhook URLs you have registered. If you have two URLs registered for one project and a message arrives, both URLs receive a `POST` in parallel — and both bodies have the same `message.id`. If a delivery is retried (after a 5xx or timeout on your side), the retry also carries the same `message.id`.
106
+
107
+
That makes `message.id` the right dedup key when one downstream consumer handles every webhook for the project:
108
+
109
+
```ts
110
+
const dedupeKey =payload.message.id;
111
+
if (awaitstore.exists(dedupeKey)) returnnewResponse('ok', { status: 200 });
If different services consume different webhook URLs and each one needs its own dedup table, scope the key with the webhook id so the same message processed by service A doesn't suppress service B:
`content` is a discriminated union tagged by `type`. It mirrors the [`message.content` shape](/spectrum-ts/content) from the `spectrum-ts` SDK.
@@ -122,7 +141,7 @@ Always handle unknown `content.type` values gracefully — new content types may
122
141
123
142
A few things that may be in the SDK's `Message` type but are intentionally **not** in the webhook payload:
124
143
125
-
- **Methods like `.reply()` or `.react()`.** They depend on a live SDK connection. To respond, call the [Spectrum API](/api-reference/introduction) using `space.id`.
144
+
- **Methods like `.reply()` or `.react()`.** They depend on a live SDK connection. To respond, run [`spectrum-ts`](/spectrum-ts/getting-started) in a separate process and call `space.send(...)` against the `space.id` you got from the webhook. There is no HTTP send endpoint yet.
126
145
- **Internal provider state.** Things like raw protocol headers, retry hints, and message acknowledgements are stripped before serialization.
127
146
- **Outbound messages.** Webhooks deliver inbound only. A message you sent does not echo back as a webhook.
Copy file name to clipboardExpand all lines: webhooks/managing-webhooks.mdx
+17-13Lines changed: 17 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,24 +7,25 @@ You manage webhooks through three HTTP endpoints on the Spectrum API. All requir
7
7
8
8
## Authentication
9
9
10
-
Every request uses HTTP Basic auth where the username is your `projectId` and the password is your `projectSecret`.
10
+
Every request uses HTTP Basic auth where the username is your `projectId` and the password is your `projectSecret`. The `projectId` also appears in the URL path — both are required.
Project credentials are scoped to a single project. They never expire — rotate them via `photon projects regenerate-secret <id>` (see the [CLI projects docs](/cli/projects#rotate-the-spectrum-api-secret)) if they leak.
17
18
18
19
## Register a webhook
19
20
20
21
```sh
21
-
curl -X POST https://spectrum.photon.codes/webhooks/ \
22
+
curl -X POST "https://spectrum.photon.codes/projects/$PROJECT_ID/webhooks/" \
|`409`| The same URL is already registered for this project | List existing webhooks, or delete the old one and re-register |
52
53
|`401`| Bad project credentials | Rotate via the CLI and try again |
53
54
54
55
### URL requirements
55
56
56
-
-Must be `https://`.
57
-
- Must be reachable from the public internet — this isn't strict, but if Spectrum can't reach it, every delivery fails.
57
+
-Should be `https://`. We accept `http://` URLs today and don't reject them at registration, but delivery is then sent in plaintext — anyone on the network path can read the payload and forge requests. Treat HTTPS as a hard requirement for any non-toy webhook.
58
+
- Must be reachable from the public internet — if Spectrum can't reach it, every delivery fails after the retry budget exhausts.
58
59
- Path component is yours to choose; we POST to it as-is.
Copy file name to clipboardExpand all lines: webhooks/overview.mdx
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
---
2
2
title: Webhooks
3
-
description: Receive iMessage and WhatsApp events at your own URL — Spectrum signs each delivery so you know it's real
3
+
description: Receive messaging events at your own URL — Spectrum signs each delivery so you know it's real
4
4
---
5
5
6
6
Spectrum webhooks push platform events — incoming messages, and (soon) more — to a URL you control. You register the URL once, and from that moment on every message that lands for your project is delivered to your server as a signed HTTP `POST`.
You write a normal HTTP handler in whatever framework you already use. Spectrum handles staying connected to iMessage and WhatsApp Business, batching, reconnects, and signing.
24
+
You write a normal HTTP handler in whatever framework you already use. Spectrum handles staying connected to every [supported platform](/spectrum-ts/providers), batching, reconnects, and signing.
25
25
26
26
## When to use webhooks
27
27
@@ -47,7 +47,7 @@ flowchart LR
47
47
Retry --> URL2
48
48
```
49
49
50
-
When a message arrives on iMessage or WhatsApp Business for a project that has webhooks registered:
50
+
When a message arrives on any enabled platform for a project that has webhooks registered:
51
51
52
52
1. Our worker receives the message from the platform.
Copy file name to clipboardExpand all lines: webhooks/quickstart.mdx
+37-17Lines changed: 37 additions & 17 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,7 +9,7 @@ If you already have a deployed HTTPS URL, skip the ngrok step.
9
9
10
10
## Prerequisites
11
11
12
-
- A Spectrum project with at least one platform enabled (iMessage or WhatsApp Business). If you don't have one yet, see[Getting Started with Spectrum](/spectrum-ts/getting-started).
12
+
- A Spectrum project with at least one platform enabled. See [Providers](/spectrum-ts/providers) for the current list, or[Getting Started with Spectrum](/spectrum-ts/getting-started) if you don't have a project yet.
13
13
- Your project id and project secret, from the [dashboard](https://app.photon.codes) or `photon projects show`.
14
14
- A reachable HTTPS URL — ngrok works for local development.
15
15
@@ -51,7 +51,7 @@ If you already have a deployed HTTPS URL, skip the ngrok step.
51
51
Use `curl` (or any HTTP client) to register the URL with your project credentials:
52
52
53
53
```sh
54
-
curl -X POST https://spectrum.photon.codes/webhooks/ \
54
+
curl -X POST "https://spectrum.photon.codes/projects/$PROJECT_ID/webhooks/" \
@@ -144,10 +144,10 @@ If you already have a deployed HTTPS URL, skip the ngrok step.
144
144
145
145
<Steptitle="Send a real message">
146
146
147
-
From your phone, send an iMessage (or WhatsApp message) to the number attached to your project. Within a second or two, your terminal should print:
147
+
Send a real message to your project from any of its enabled platforms — an iMessage to the assigned number, a WhatsApp message, whatever you've configured. Within a second or two, your terminal should print:
148
148
149
149
```text
150
-
message from imessage:+15551234567 : { type: 'text', text: 'hi' }
150
+
message from +15550100 : { type: 'text', text: 'hi' }
151
151
```
152
152
153
153
If you see `bad signature`, double-check that you exported `SPECTRUM_SIGNING_SECRET` correctly and re-started the server.
@@ -157,22 +157,42 @@ If you already have a deployed HTTPS URL, skip the ngrok step.
157
157
158
158
<Steptitle="Reply from your handler (optional)">
159
159
160
-
The webhook delivery only carries the inbound message. To reply, call the [Spectrum API](/api-reference/introduction) (or run a separate `Spectrum()` instance for sending). A typical pattern:
160
+
The webhook delivery only carries inbound messages — there is no public HTTP "send a message" endpoint today. To reply, run the [`spectrum-ts`](/spectrum-ts/getting-started) SDK in a separate process (or alongside your handler) and call `space.send(...)` there. The webhook tells your service *what* arrived; the SDK is what puts a message back on the wire.
161
+
162
+
A common split:
161
163
162
164
```ts
163
-
if (event==='messages'&&payload.message.content.type==='text') {
Acknowledge the webhook (`return c.text('ok', 200)`) **before**the reply call if your reply might take >1s — the worker treats a slow response as a timeout and retries.
195
+
An HTTP send-message API is on the roadmap; until then, the SDK is the supported path.
176
196
</Step>
177
197
</Steps>
178
198
@@ -181,7 +201,7 @@ If you already have a deployed HTTPS URL, skip the ngrok step.
181
201
You have an end-to-end pipeline:
182
202
183
203
```text
184
-
phone → iMessage → Spectrum → POST /spectrum-webhook → your code
204
+
user's device → platform → Spectrum → POST /spectrum-webhook → your code
185
205
```
186
206
187
207
You verified each delivery is genuine (not spoofed), recent (not a replay), and unmodified (not tampered with).
The URL you expect should appear in the list. If not, register it.
@@ -76,7 +77,7 @@ Walk through this checklist in order:
76
77
photon spectrum platforms ls
77
78
```
78
79
79
-
An iMessage line that's not paired or a WhatsApp Business token that's expired produces zero inbound events. Webhooks deliver what the SDK receives — if the SDK is silent, webhooks are silent.
80
+
A platform that's enabled in the dashboard but not actually connected — an unpaired iMessage line, an expired WhatsApp token, a custom provider whose lifecycle handler is throwing — produces zero inbound events. Webhooks deliver what the SDK receives, so if the SDK is silent for a platform, that platform's webhooks are silent too. Check the SDK side first.
80
81
</Step>
81
82
82
83
<Steptitle="Confirm the message is actually inbound to your project">
@@ -153,7 +154,7 @@ Free ngrok tunnels get a new URL every restart. That URL won't be registered wit
153
154
```sh
154
155
ngrok http 3000
155
156
# Copy the new URL, then:
156
-
curl -X POST https://spectrum.photon.codes/webhooks/ \
157
+
curl -X POST "https://spectrum.photon.codes/projects/$PROJECT_ID/webhooks/" \
0 commit comments