Skip to main content
Vercel eve agents talk through channels. This guide wires a custom imessage channel to Comms so any eve agent can hold conversations over iMessage and SMS: a user texts your Comms number, Comms fires a webhook, the channel starts or resumes a durable session, and the agent’s reply goes back out through the send message endpoint.

How it maps

Prerequisites

  • An eve agent project you can deploy with eve deploy.
  • A Messages API key with the comms_send scope (replies) and one with comms_webhooks (registration) — one key with both scopes is fine.
  • A Comms line. New workspaces get one at comms.osis.co.

Set up the channel

1

Add the channel file

Save the source below as agent/channels/imessage.ts in your eve project. The file stem becomes the channel id, so the webhook route mounts at /eve/v1/imessage/webhook.
imessage.ts
2

Configure environment variables

The channel fails closed twice over: no COMMS_WEBHOOK_SECRET means every webhook is rejected, and no IMESSAGE_ALLOW_FROM means every sender is dropped. Set both before expecting replies — and treat * as a decision, not a default. An open allowlist lets anyone with your number run your agent.
3

Deploy

Note the deployment URL — the webhook lives at https://<your-agent-domain>/eve/v1/imessage/webhook.
4

Register the webhook with Comms

The 201 response includes the endpoint’s secret (whsec_…). Set it as COMMS_WEBHOOK_SECRET on the eve deployment and redeploy.
5

Verify with a ping

Fire a signed test event at the endpoint (use the id from the registration response):
The channel answers comms.ping with { "ok": true, "pong": true } — check with list events that the delivery succeeded. Then text the line. Send /new at any time to retire the session and start fresh.

What arrives at the channel

Comms delivers the standard webhook envelope, signed with X-Osis-Signature: sha256=<hex> — an HMAC-SHA256 of the raw request body using the endpoint secret:
The channel verifies the signature with a timing-safe compare before anything else runs, then uses conversation_id as the continuation token and contact.phone as the principal.

Behavior notes

Comms delivers webhooks at-least-once (up to 6 attempts with backoff). The channel dedupes by message id; if dispatch into eve fails it releases the dedupe claim and returns 500, so the retry gets a clean run.
Every sender in a conversation shares one session — same conversation_id, same continuation token — while each message carries that sender’s phone as principalId and their name in auth.attributes.senderName, so the agent knows who said what.
Replies target the conversation_id (falling back to a direct send by phone) and carry an idempotency key derived from session and turn, so a double-fired event can’t double-text a customer. Outbound retries on 429/5xx honor Retry-After.
iMessage has no buttons, so input.requested prompts go out as plain text. The user’s next reply flows into the same session and resolves the pending request.
The channel parses an attachments array into file parts and fetches authenticated URLs with the API key (same-origin only, 20 MB cap). comms.message.received payloads are text-only today — the path activates automatically once attachments ship in the payload.

Programmatic configuration

The default export reads env vars. For code-level control — a dynamic allowlist, or vetoing messages before they reach the agent — build the channel yourself:
imessageContinuationToken(chatId) is exported for cross-channel hand-offs via helpers.receive("imessage", …).