Errors and limits
Handle validation errors, sender problems, quota limits, and rate limits cleanly.
How failures are returned
Synchronous API errors mean the request was not accepted. Message status failures mean the request was accepted but delivery later failed.
Your integration should handle both. Store `message_id`, poll status when needed, and surface clear recovery instructions for operators.
Validation errors from Zod return HTTP 422 with `error`, `message`, and `details`. Application errors return `error`, `message`, and optional `details`.
{
"error": "validation_error",
"message": "Request validation failed",
"details": {
"fieldErrors": {
"message": ["message or attachment is required"]
},
"formErrors": []
}
}Common status codes
The backend uses standard HTTP status codes for authentication, validation, sender assignment, and rate limiting failures.
Exact error codes
Use the machine-readable `error` field for control flow. Use `message` for logs and operator-facing guidance.
Error examples
These examples show the response shapes most integrations should handle explicitly.
[
{
"status": 401,
"body": {
"error": "unauthorized",
"message": "Invalid x-api-key"
}
},
{
"status": 403,
"body": {
"error": "forbidden",
"message": "API client is not assigned to this sender"
}
},
{
"status": 409,
"body": {
"error": "sender_not_connected",
"message": "Sender is not connected (status: logged_out). Please scan QR again."
}
},
{
"status": 429,
"body": {
"error": "rate_limited",
"message": "Too many requests. Slow down."
}
},
{
"status": 429,
"body": {
"error": "daily_limit_reached",
"message": "Sender reached its daily limit."
}
},
{
"status": 429,
"body": {
"error": "monthly_quota_exceeded",
"message": "Workspace monthly message quota has been reached."
}
},
{
"status": 422,
"body": {
"error": "receiver_blocked",
"message": "Number 212612345678 is blocked."
}
},
{
"status": 404,
"body": {
"error": "not_found",
"message": "Message 2b7a0bd5-1a0d-4e6c-85b1-a7f44f92dfb0 not found"
}
},
{
"status": 409,
"body": {
"error": "duplicate_idempotency_key",
"message": "A message with this idempotency key already exists."
}
}
]Rate and quota controls
The platform enforces general API rate limits, client API rate limits, OTP-specific rate limits, receiver-level OTP limits, sender daily limits, and monthly plan limits.
Do not retry 429 responses immediately. Use backoff and show a clear operator message.
Rate limit headers
HTTP rate-limited responses include standard rate-limit headers from the Redis-backed limiter. Use them to delay retries instead of guessing.
`Retry-After` is present when the request exceeds the current window.
Retry guidance
Retry only failures that can succeed without changing the request. Never retry invalid input, wrong credentials, wrong sender assignment, blocked receivers, or exhausted quota in a tight loop.
async function sendWithBackoff(request: () => Promise<Response>) {
for (let attempt = 0; attempt < 3; attempt += 1) {
const response = await request();
if (response.ok) return response.json();
const retryAfter = Number(response.headers.get("Retry-After") ?? "0");
if (![429, 500, 502, 503].includes(response.status)) {
throw new Error(await response.text());
}
const delayMs = retryAfter > 0 ? retryAfter * 1000 : 500 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
throw new Error("HookMessage request failed after retries");
}Python backoff
Use the same retry rule in Python: respect `Retry-After`, retry only temporary failures, and keep the same idempotency key.
import time
def send_with_backoff(call):
for attempt in range(3):
response = call()
if response.status_code < 400:
return response.json()
if response.status_code not in (429, 500, 502, 503):
raise RuntimeError(response.text)
retry_after = int(response.headers.get("Retry-After") or 0)
delay = retry_after if retry_after > 0 else 0.5 * (2 ** attempt)
time.sleep(delay)
raise RuntimeError("HookMessage request failed after retries")Need help?
Use Book Integration Help if you want setup help for OTP or automated customer messaging.
Next steps
Quickstart
A practical path from empty workspace to a working server-side integration.
Authentication and access
HookMessage separates customer API keys, dashboard sessions, and internal platform access.
Workspaces
A workspace is the tenant boundary for every customer resource.
Senders and sessions
A sender is the WhatsApp number used to deliver messages for a workspace.