GET /messages.
Building with the Messages API only? In the dashboard open Lines and choose API / webhooks (or claim a dedicated number with “Use API / webhooks”). That attaches the line to your account without a live no-code agent, so you do not get dual replies from both the agent and your webhook handler. You can also pause any existing agent on the line.
Prerequisites
- A Messages API key with the
comms_webhooksscope. - A public HTTPS URL that accepts
POST. - Prefer API / webhooks inbound mode on Lines so no-code agents do not auto-reply alongside your backend.
Register an endpoint
comms_webhooksSuccess: 201 with the created webhook object, including its signing secret:
webhook.secret is generated server-side on create — you never supply one. Store it wherever your handler runs; you’ll use it to verify every delivery. If you lose it, list webhooks returns it again for your endpoints. The Developers → Webhooks UI also reveals the secret immediately after create.
Omitting events subscribes the endpoint to every comms event.
Which workspace gets the event?
Outbound webhooks are workspace-scoped: every matching event for lines owned by your org is delivered to your endpoints. There is no per-line webhook filter on the endpoint object. On a shared platform line, inbound traffic is routed to a workspace by claim keyword / sticky assignment for that contact. The org that owns the resolved route receivescomms.message.* (and call/consent) events. Dedicated lines always belong to a single workspace.
Every message event includes the line the contact reached (or that you sent from), when known:
Use
line_e164 as the tenant discriminator when one workspace owns more than one dedicated number. You do not need a dummy no-code agent per customer.
Message edit events
When a contact edits an iMessage, the messaging bridge emitsmessage.edited (header X-Comms-Event: message.edited). Comms updates the stored message and fans out:
Payload shape matches other message events, with extra fields on
data.message:
comms.message.edited (or omit events to receive all).
Message reaction events
When a contact adds or removes a native iMessage tapback, the messaging bridge emitsmessage.reaction (header X-Comms-Event: message.reaction). Comms stores the reaction on the target message and fans out:
409 unsupported_transport.
List endpoints
Verify signatures
Every delivery is aPOST with two headers:
To verify: compute
HMAC_SHA256(secret, rawBody), hex-encode it, and constant-time compare against the header value after stripping the sha256= prefix. Read the raw bytes before any JSON parsing — re-serialized JSON will not match.
Send a test event
Fire a signedcomms.ping at your endpoint without generating real traffic:
2xx from your handler proves the whole path works. The test request waits for that attempt and returns its delivery status. The endpoint must include comms.ping in its events; otherwise the test returns an error immediately and does not enqueue a delivery.
Local development
- Run your app with a
POSThandler that logs the body. - Tunnel with ngrok / Cloudflare Tunnel.
- Register the tunnel URL as above.
- Send a test message to your line, or fire a
comms.pingwith the test endpoint.
Handler checklist
Verify before trusting
Verify before trusting
Reject any request whose
X-Osis-Signature doesn’t verify against your whsec_ secret. See Verify signatures.Respond quickly
Respond quickly
Return 2xx as soon as you accept the event. Do heavy work in a queue.
Treat delivery as at-least-once
Treat delivery as at-least-once
The same event may arrive more than once. Make handlers idempotent on event id when present.
Keep secrets off the client
Keep secrets off the client
Webhook receivers belong on your server. Do not process hooks in a browser.
Related
- Create webhook
- List webhooks
- List events — poll delivery attempts if you need pull-based debugging