Public API
Authentication
Every Public API request is authenticated with an API key. Optional layers — HMAC signatures, IP allowlists, and resource whitelists — let you harden the surface as needed.
Bearer token
Pass the API key plaintext (prefix sflo_live_) in the Authorization header:
Authorization: Bearer sflo_live_AbCdEf...XYZThe server stores only an HMAC hash of the key—your plaintext is unrecoverable after the key is created. Treat keys like passwords: rotate on compromise, scope them tightly, and never embed in client code.
Scopes
Keys carry an immutable list of resource:action scopes. Each endpoint declares its required scope; requests missing the scope are rejected with 403 Forbidden and Meta error code 200 (Permission denied).
| Scope | Endpoints |
|---|---|
| messages:send | POST /v1/:phoneNumberId/messages |
| templates:read | GET /v1/:wabaId/message_templates |
| templates:write | POST /v1/:wabaId/message_templates |
| contacts:read | GET /v1/contacts* |
| contacts:write | POST/PATCH/DELETE /v1/contacts* |
| blacklist:read | GET /v1/blacklist* |
| blacklist:write | POST/PATCH/DELETE /v1/blacklist* |
Use the smallest set of scopes that satisfies your integration. A marketing scheduler should hold only messages:send; a CRM sync job should hold contacts:read + contacts:write and nothing else.
:phoneNumberId (messages.send today). Tenant-level resources like /v1/contacts and /v1/blacklistare governed by tenant scope alone—every key can see every contact in its tenant.HMAC request signing (optional)
Keys created with Require signature on must include signed timestamps on every request. This prevents bearer-token leaks from being replayable—even if your key escapes a CI log, the attacker also needs the signing secret to forge a valid call.
Two headers are required when signing is enabled:
X-SigFollow-Timestamp— current Unix epoch secondsX-SigFollow-Signature—sha256=<hex_hmac>over the canonical input
The signed input is:
${timestamp}.${HTTP_METHOD}.${path}.${sha256_hex(raw_body)}Concretely: timestamp, HTTP method (uppercase), request path (excluding host and query string), and SHA-256 hex of the raw request body, joined with literal periods. HMAC-SHA256 with the signing secret produces the signature.
Example signer in Node.js:
import crypto from "node:crypto";
function sign({ method, path, body, signingSecret }) {
const timestamp = Math.floor(Date.now() / 1000);
const bodyHash = crypto
.createHash("sha256")
.update(body ?? "", "utf8")
.digest("hex");
const signed = `${timestamp}.${method.toUpperCase()}.${path}.${bodyHash}`;
const signature = crypto
.createHmac("sha256", signingSecret)
.update(signed)
.digest("hex");
return {
"X-SigFollow-Timestamp": String(timestamp),
"X-SigFollow-Signature": `sha256=${signature}`,
};
}|now - timestamp| > 300 seconds to bound replay risk. Keep your client clock in sync (NTP)./media upload endpoint is exempt from HMAC checks even when the key requires signing — Nest cannot reliably hash a streamed multipart body. API key auth, rate limiting, IP allowlists, and scope checks still apply.IP allowlist (optional)
Each key can carry a CIDR allowlist (IPv4 / IPv6). When set, requests from any other IP are rejected with 403. Useful when your integration runs from a fixed egress (e.g. a CRM in a single VPC).
trust proxy is configured correctly on your SigFollow gateway, otherwise the IP check sees the proxy address instead of the real client.What happens on failure
- Missing / malformed
Authorization→401, Meta error code190, headerWWW-Authenticate: Bearer - Key revoked / expired →
401, code190 - Missing scope →
403, code200 - IP rejected →
403, code200 - HMAC missing / invalid / replayed →
401, structured error
See the full error reference for body schemas.