Build your connection.
Verify your email, create a WhatsApp session in your workspace, and scan its QR code. When its state is ready, create an API key with send permission. Connection lifecycle and QR access require the separate manage permission. Save the full token when it is shown; only its hash is retained.
Send your first text
curl -X POST https://whatsapp.turbineweb.com/api/v1/messages/text \
-H 'Authorization: Bearer tw_live_YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: order-1042-confirmed' \
-d '{
"channel_id": "YOUR_SESSION_ID",
"to": "14155550123",
"body": "Your order is confirmed."
}'Use an international number without spaces or a leading plus. An HTTP 202 response means the request entered the queue; it does not guarantee delivery. Trial messages can go only to your connected number or an admin-approved test recipient.
Follow a message
GET /api/v1/messages/{message_id}
Authorization: Bearer tw_live_YOUR_READ_TOKEN
{ "data": { "id": "…", "status": "sent" } }States are queued, sending, sent, delivered, read, rejected, and uncertain. If the gateway loses its response during a send, the message becomes uncertain and is never automatically sent again. Check WhatsApp before deliberately creating a replacement. Delivery and read states depend on events available from WhatsApp; some messages never provide read receipts.
Idempotency and limits
Every send requires a unique Idempotency-Key, 8–100 characters from letters, digits, underscores, and hyphens. Reusing it with the same request returns its original message. Reusing it with different content returns HTTP 409. Allowances apply atomically across all tokens and sessions in your account, before queueing. UTC calendar boundaries determine daily and monthly limits.
API endpoints
| Method | Endpoint | Permission |
|---|---|---|
| GET | /api/v1/channels | read |
| POST | /api/v1/channels | manage |
| GET | /api/v1/channels/{id}/status | read |
| GET | /api/v1/channels/{id}/qr | manage |
| POST | /api/v1/channels/{id}/start | manage |
| POST | /api/v1/channels/{id}/stop | manage |
| POST | /api/v1/channels/{id}/delete | manage |
| POST | /api/v1/messages/text | send |
| POST | /api/v1/messages/image | send · paid |
| POST | /api/v1/messages/document | send · paid |
| GET | /api/v1/messages/{id} | read |
| GET | /api/v1/channels/{id}/contacts | read |
| GET | /api/v1/channels/{id}/groups | read |
| GET | /api/v1/channels/{id}/chats | read |
Images and documents
Use the image or document endpoint with channel_id, to, base64, mimetype, and optional filename and caption. Maximum decoded media size is approximately 1 MB. Accepted types are image/jpeg, image/png, and application/pdf. Remote media URLs are not accepted, which prevents customer requests from making the gateway access private services.
Verify webhook signatures
Add your public HTTPS destination in Webhooks. Save its one-time signing secret. Events are delivered with X-TurbineWhats-Event, X-TurbineWhats-Timestamp, and X-TurbineWhats-Signature headers. The signature is HMAC-SHA256 over timestamp + a period + the exact raw request body.
import { createHmac, timingSafeEqual } from 'node:crypto';
const timestamp = req.headers['x-turbinewhats-timestamp'];
if (Math.abs(Date.now()/1000 - Number(timestamp)) > 300) {
throw new Error('Expired event');
}
const expected = 'sha256=' + createHmac('sha256', secret)
.update(timestamp + '.' + rawBody).digest('hex');
const actual = req.headers['x-turbinewhats-signature'] || '';
if (actual.length !== expected.length ||
!timingSafeEqual(Buffer.from(actual), Buffer.from(expected))) {
throw new Error('Invalid signature');
}
// Deduplicate X-TurbineWhats-Event in your database.
// Return 2xx after safely recording the event.Delivery retries use exponential backoff with at most five attempts. Redirects are not followed. Only public IPv4 HTTPS destinations on port 443 are accepted; private and link-local addresses are refused on every delivery. Treat webhook data as untrusted input and deduplicate by event ID.
Errors you can act on
401: invalid or expired token. 403: permission, ownership, or trial restriction. 402: trial or paid period expired. 409: session not ready or an idempotency conflict. 429: request rate or sending allowance reached. 503: an integration is not configured. Errors return an error object with code and message. Do not retry an uncertain send with a new key automatically.
Use it responsibly
Send only expected messages to consenting recipients. Unsolicited bulk outreach, purchased lists, impersonation, fraud, and limit evasion are prohibited. The connection is independent of Meta and can be interrupted or restricted by WhatsApp. Keep a separate channel for critical notifications.