REST API and webhooks
Authenticate with an API key, call the v1 endpoints, and verify the signature on every webhook Ritmo sends you.
Every workspace can issue API keys from Settings, API. A key acts as the whole workspace, so treat it like a password: it is shown once, at creation, and nothing can display it again.
Authentication
Send the public key and the secret together as a bearer token, separated by a colon. The public half is safe to log; the secret half is not.
curl https://ritmo-api.uritm.com/api/v1/contacts \
-H "Authorization: Bearer rk_live_XXXX:YOUR_SECRET"Scopes
A key created with no scopes can do everything the workspace can. Pick scopes to narrow it — a reporting script given only contacts:read cannot send a message even if it is compromised.
- contacts:read and contacts:write
- messages:read and messages:write
- otp:send
- templates:read
- broadcasts:read and broadcasts:write
- usage:read
Endpoints
- GET /api/v1/contacts — paged, accepts search, page and pageSize
- POST /api/v1/contacts — create a contact
- GET /api/v1/segments — your saved audience filters
- POST /api/v1/messages — reply in an existing conversation
- POST /api/v1/otp/send — send an SMS through your own provider account
- GET /api/v1/templates — approved WhatsApp templates
- GET /api/v1/broadcasts and POST /api/v1/broadcasts/:id/start
- GET /api/v1/usage — this month, by channel
Webhooks
Add an endpoint in Settings, API. It must be an https URL on a public host: Ritmo refuses private addresses, because a webhook is a request our servers make on your say-so.
Each delivery carries an X-Ritmo-Signature header shaped like a Stripe signature, with the timestamp inside the signed payload so a captured request cannot be replayed later.
t=1735689600,v1=5257a869e7ecebeda32affa62cdca3faVerify it by recomputing an HMAC-SHA256 of the timestamp, a full stop, and the raw request body, using your endpoint secret. Compare in constant time, and reject anything more than five minutes old.
const [t, v1] = header.split(",").map((p) => p.split("=")[1]);
const expected = crypto
.createHmac("sha256", endpointSecret)
.update(`${t}.${rawBody}`)
.digest("hex");
const ok =
crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected)) &&
Math.abs(Date.now() / 1000 - Number(t)) < 300;Retries
Answer with any 2xx status. Anything else is retried eight times over about four hours with growing gaps. Twenty consecutive failures disable the endpoint and we tell your admins, rather than retrying a dead server forever.