Skip to content

Commit db9a941

Browse files
yanxue06cursoragent
andcommitted
docs(webhooks): extract byte retrieval into dedicated section with SDK example
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 4aa2965 commit db9a941

1 file changed

Lines changed: 36 additions & 1 deletion

File tree

docs-src/webhooks/events.mdx.vel

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,44 @@ type Content =
178178
| `application/*` | Document or file | `application/pdf`, `application/zip` |
179179

180180
<Warning>
181-
**Byte-bearing arms ship metadata, not bytes.** `attachment`, `voice`, and `contact.photo` all carry `mimeType` / `size` / filename — never the raw bytes themselves and never a download URL. To process the actual content you need an additional retrieval step. Hold a [`spectrum-ts`](/spectrum-ts/getting-started) instance in a long-lived process and call its byte accessors, or contact support if you need attachment retrieval — the `message.id` from the webhook is the canonical handle any future retrieval API will accept. A first-class HTTP download endpoint is on the roadmap.
181+
**Byte-bearing arms ship metadata, not bytes.** `attachment`, `voice`, and `contact.photo` carry `mimeType` / `size` / filename — never raw bytes and never a download URL. See [Retrieving attachment bytes](#retrieving-attachment-bytes) below.
182182
</Warning>
183183

184+
##### Retrieving attachment bytes
185+
186+
The webhook payload tells you an attachment exists and hands you its metadata. To process the actual file bytes, you need a separate retrieval step. Two options today:
187+
188+
**Option 1 — `spectrum-ts` SDK in a long-lived process**
189+
190+
```ts
191+
import { Spectrum } from 'spectrum-ts';
192+
import { imessage } from 'spectrum-ts/providers/imessage';
193+
194+
// One Spectrum instance per process, reused across requests
195+
const app = await Spectrum({
196+
projectId: process.env.PROJECT_ID!,
197+
projectSecret: process.env.PROJECT_SECRET!,
198+
providers: [imessage.config()],
199+
});
200+
201+
async function fetchAttachment(payload: WebhookEventPayload): Promise<Buffer> {
202+
const space = await app.spaces.get(payload.space.id);
203+
const message = await space.getMessage(payload.message.id);
204+
if (message?.content.type !== 'attachment') {
205+
throw new Error('Expected attachment content');
206+
}
207+
return await message.content.read();
208+
}
209+
```
210+
211+
This works today. **Don't construct a new `Spectrum()` per request** — construction hits a rate-limited cloud API and will 429 under any real volume. Hoist to module scope and share across requests.
212+
213+
**Option 2 — HTTP download endpoint (on the roadmap)**
214+
215+
A first-class `GET /v1/messages/{messageId}/attachment` endpoint that returns bytes directly is planned. Until it ships, contact support for attachment retrieval in production environments where Option 1 isn't viable (serverless or edge runtimes, etc.).
216+
217+
The `message.id` from the webhook is the canonical handle any future retrieval API will accept.
218+
184219
##### Per-arm wire reference
185220

186221
Every arm of the `Content` union, post-projection. Hand-written rather than vellum-driven because the wire shapes deliberately diverge from the SDK shapes (function thunks dropped, server-internal fields suppressed, recursive targets reduced to slim refs).

0 commit comments

Comments
 (0)