All guides

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=5257a869e7ecebeda32affa62cdca3fa

Verify 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.