Developers and the API
Relay has a REST API for building your own integrations, and outgoing webhooks for reacting to events in real time. Both live under Settings → API keys and Settings → Webhooks, admin and owner only.
API keys
An API key authenticates requests to Relay's public REST API. Create one from Settings → API keys → New API key: give it a name (name it after the integration that will use it) and pick only the scopes it needs.
Available scopes:
| Scope | Grants |
|---|---|
messages:send | Send messages |
messages:read | Read messages |
contacts:read | Read contacts |
contacts:write | Create and update contacts |
conversations:read | Read conversations |
broadcasts:send | Send campaigns |
webhooks:manage | Create, update and delete webhook endpoints |
A key's scopes are all that gates what it can do — they're independent of the role of whoever created it. A key with no scopes at all can still call GET /api/v1/me to verify it works.
REST API basics
All public endpoints live under /api/v1 and require a bearer key:
Authorization: Bearer relay_live_xxxxxxxxxxxx
A few things that hold across every endpoint:
- Account-scoped. A key only ever sees and acts on the account it belongs to.
- Multi-number aware. If your account has more than one connected number, pass
?from=num_<uuid>to target a specific one; omitting it uses your default number. - Response envelope. Successful responses are wrapped as
{ "data": ... }; errors as{ "error": { "code": "...", "message": "..." } }. - Rate limited. 120 requests per minute per key. Going over returns
429 rate_limitedwith aRetry-Afterheader (andX-RateLimit-*headers on every response so you can see how close you are). GET /api/v1/meis the discovery endpoint — it tells you the account, the key's scopes, and the numbers available to it, which is the fastest way to sanity-check a new key.
Error codes worth knowing
| HTTP | Code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing or invalid key |
| 402 | upgrade_required | The action isn't included in your plan |
| 402 | insufficient_balance | Your plan allows it, but the prepaid wallet doesn't have enough funds |
| 403 | forbidden | Key doesn't have the required scope |
| 429 | rate_limited | Over 120 requests/minute |
| 400 | bad_request | Malformed request |
| 404 | not_found | No such resource for this account |
| 500 | internal | Something went wrong on Relay's side |
upgrade_required and insufficient_balance are deliberately distinct 402s — one means "change your plan," the other means "top up the wallet" (see Usage and limits and Wallet).
Outgoing webhooks
Webhooks push events to your own HTTPS endpoint in real time, instead of you polling the API. Create one from Settings → Webhooks → New webhook: give it a URL (must be https://) and pick which events it should receive.
Available events:
| Event | Fires when |
|---|---|
message.received | An inbound WhatsApp message arrives |
message.status_updated | A sent message's delivery status changes (sent, delivered, read, failed) |
conversation.created | A new conversation is started |
Verifying a delivery
Every delivery is signed. Relay sends a header:
X-Relay-Signature: t=<unix_seconds>,v1=<hex>
v1 is HMAC-SHA256(secret, "${t}.${rawBody}"), computed with the endpoint's signing secret. Verify it by recomputing the same HMAC over the timestamp and raw request body and comparing — reject anything that doesn't match, and reject stale timestamps to guard against replay.
A few operational notes:
- Endpoints are checked against SSRF risks (no pointing a webhook at your own internal network) before Relay will accept them.
- An endpoint that fails delivery repeatedly, past a threshold, is automatically disabled — a warning appears once it's most of the way there, so you have a chance to fix it first. A disabled endpoint has to be re-enabled manually once the underlying problem is fixed.
- Webhooks are a plan-gated feature and the number of endpoints you can create is capped by your plan (see Usage and limits).
The relay-mcp server
relay-mcp is a small Model Context Protocol server that wraps the public API, so an AI tool (Claude, or anything else that speaks MCP) can act on your Relay account directly. Point it at your account with two environment variables:
{
"mcpServers": {
"relay": {
"command": "npx",
"args": ["-y", "relay-mcp"],
"env": {
"RELAY_BASE_URL": "https://your-relay-domain.example.com",
"RELAY_API_KEY": "relay_live_xxxxxxxxxxxx"
}
}
}
}
By default it's read-only — tools like list_contacts, get_contact, list_conversations, list_messages and get_broadcast work out of the box, but nothing that writes or sends does. Opt in explicitly per capability:
RELAY_ENABLE_WRITES=true— unlockssend_message,create_contact,update_contact.RELAY_ENABLE_BROADCASTS=true— unlockssend_broadcast, which additionally requiresconfirm: trueon every call since it's marked destructive.
Who can manage this
API keys and webhooks are admin-and-owner gated, same as the rest of account-wide settings (see Members and roles).