Introduction

The TrustForce Hosting API lets your app send and receive WhatsApp messages, manage automated keyword replies, and pull message history — all through one REST API backed by your linked WhatsApp number(s).

Base URL for every endpoint below:

https://wa.trustforce.co.za/api/v1/

Authentication

Every protected request needs your API key (from Dashboard → API keys) in the Authorization header, or X-API-Key if your hosting strips Authorization headers:

Authorization: Bearer tfh_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Most endpoints also accept an optional session parameter — the session key shown on your WhatsApp number's card in the dashboard. If you leave it out, the key's default number is used.

Errors

Errors come back as JSON with an error field and a standard HTTP status code:

CodeMeaning
401Missing, invalid, or revoked API key
403Key doesn't have the required permission, or account suspended
404Endpoint or resource not found
409WhatsApp number not connected yet
422Missing or invalid parameter
429Plan limit reached (messages, webhooks, or rules)
502The Netronical server returned an error

Send a message

Quick testers included in the project root: /test.php for send-only and /api-test.php for health, sessions, status, webhooks, rules, logs, and optional sending.

POST /api/v1/send-message
POST /api/v1/send
POST /api/v1/send

Requires the send scope. Sends a text message, image, document, or video by URL. Authentication supports Authorization: Bearer <api_key> or X-API-Key: <api_key>.

ParamDescription
toDestination number, digits with country code (e.g. 27825550142)
messageText body (required unless sending media). text is also accepted.
media_urlPublic URL of an image/document to send instead of text
media_typeimage | document | video (default image)
captionOptional caption for media
sessionOptional session key, if not using the key's default
# Example curl -X POST https://wa.trustforce.co.za/api/v1/send-message \ -H "Authorization: Bearer tfh_live_xxxx" \ -H "X-API-Key: tfh_live_xxxx" \ -H "Content-Type: application/json" \ -d '{"to":"27825550142","message":"Hello from my app!"}'
// 200 response { "success": true, "to": "27825550142@c.us", "data": { "...Netronical server response..." } }

Session status

GET /api/v1/status?session=tfh_u12_a9f3

Requires the read scope. Returns connection state for one number.

{ "session": "tfh_u12_a9f3", "label": "Support line", "status": "connected", "phone_number": "27825550142", "connected_at": "2026-05-02 10:14:00" }

List your numbers

GET /api/v1/sessions

Requires the read scope. Returns every WhatsApp number on your account.

Webhooks

GET /api/v1/webhooks
POST /api/v1/webhooks

Requires the webhook scope. List or create webhooks. Creating one returns a forward_secret used to sign forwarded events (see below).

Param (POST)Description
sessionSession key this webhook listens on
nameA label for the webhook
forward_urlOptional — your server's URL to receive raw events

Keyword rules

GET /api/v1/rules?webhook_id=4
POST /api/v1/rules
DELETE /api/v1/rules

Requires the webhook scope. Manage the keyword → auto-reply rules attached to a webhook.

ParamDescription
webhook_idRequired on every call
keywordThe text or regex pattern to match (POST)
match_typecontains | exact | starts_with | regex
case_sensitivetrue/false, default false
reply_typetext | image | document | no_reply
reply_textReply body or caption
reply_media_urlURL for image/document replies
priorityLower number is checked first
idRule id (DELETE only)
# Auto-reply to anything containing "price" curl -X POST https://wa.trustforce.co.za/api/v1/rules \ -H "Authorization: Bearer tfh_live_xxxx" \ -H "X-API-Key: tfh_live_xxxx" \ -H "Content-Type: application/json" \ -d '{ "webhook_id": 4, "keyword": "price", "match_type": "contains", "reply_type": "text", "reply_text": "Our plans start at R50/month — want the full list?" }'

Message logs

GET /api/v1/logs?limit=50

Requires the read scope. Returns your most recent inbound and outbound messages, newest first.

Your SMS number

Every account needs exactly one dedicated SMS number before it can send or receive SMS — numbers aren't shared between customers. Claim one from the dashboard (Your SMS number) or via the API below.

GET /api/v1/sms/number
POST /api/v1/sms/number
DELETE /api/v1/sms/number

GET requires read; POST/DELETE require send. POST body: {"device_id": 3} — get available device IDs from GET /api/v1/sms/devices first. DELETE releases your number back to the available pool.

Send an SMS

POST /api/v1/sms/send

Requires the send scope, and a claimed number (see above) — sends always go out from your assigned number, never a shared pool. SMS is billed from your prepaid SMS credits — a credit is only ever spent once the send has actually succeeded. (If your account has been given a monthly SMS allowance directly by support, that's used first before credits.)

ParamDescription
toDestination number, with country code (e.g. +27825550142)
messageText body (required). text / content also accepted.
scheduled_atOptional. ISO 8601 date/time — queues the send instead of sending inline (see below).
# Example curl -X POST https://wa.trustforce.co.za/api/v1/sms/send \ -H "Authorization: Bearer tfh_live_xxxx" \ -H "Content-Type: application/json" \ -d '{"to":"+27825550142","message":"Your order has shipped"}'
// 201 response { "success": true, "message_id": "msg_1042", "to": "27825550142", "status": "sent", "billed_via": "plan" }

billed_via is "plan" when it came out of your monthly allowance, or "credit" when it was billed against your prepaid credit balance. A 409 means you haven't claimed a number yet; a 429 means your allowance and credits are both exhausted.

Bulk & scheduled SMS

POST /api/v1/sms/bulk-send

Requires the send scope. Queues up to 500 messages at once instead of sending inline — each is picked up by the send queue within a minute or so. Billing (credits, or a support-granted allowance if you have one) is checked per-message at send time, not when you submit the batch.

# Same message to many recipients curl -X POST https://wa.trustforce.co.za/api/v1/sms/bulk-send \ -H "Authorization: Bearer tfh_live_xxxx" \ -H "Content-Type: application/json" \ -d '{"to":["+27825550142","+27825550143"],"message":"Sale ends tonight!"}'
# Different message per recipient, and scheduled { "messages": [ { "to": "+27825550142", "message": "Hi Sarah, your appointment is tomorrow at 10am" }, { "to": "+27825550143", "message": "Hi John, your appointment is tomorrow at 2pm" } ], "scheduled_at": "2026-07-25T08:00:00+02:00" }

Every POST /api/v1/sms/send also accepts scheduled_at directly for a single scheduled message. Scheduled/queued messages appear immediately in GET /api/v1/sms/messages with status "scheduled" or "queued", then update to "sent"/"failed" once the queue processes them.

SMS message history

GET /api/v1/sms/messages?limit=50
GET /api/v1/sms/messages?id=msg_1042

Requires the read scope. Lists your SMS history, or fetches a single message by its message_id.

SMS device pool

GET /api/v1/sms/devices

Requires the read scope. Returns available numbers plus your own (never another customer's assigned number) — label, number, active status, last heartbeat. The underlying gateway credentials are never exposed.

SMS webhook

GET /api/v1/sms/webhook
POST /api/v1/sms/webhook
DELETE /api/v1/sms/webhook

Requires the webhook scope. Configure one forward URL to receive incoming SMS and delivery-status updates. Creating one returns a forward_secret — events are signed with it the same way WhatsApp forwarded events are, in the X-TFH-SMS-Signature header.

// Example forwarded event { "event": "sms.received", "data": { "from": "27825550142", "to": "27821234567", "content": "yes please", "message_id": "..." }, "timestamp": "2026-07-23T10:15:00+00:00" }

Other event types: sms.status_updated (data: message_id, statussent / delivered / failed / expired).

Receiving messages: forwarded events

If you set a forward_url on a webhook, every inbound message on that number is POSTed to your server as it arrives — whether or not it matched a keyword rule:

{ "event": "message.received", "webhook_id": 4, "message": { "from": "27825550142@c.us", "body": "do you ship to durban?", "...raw Netronical event fields..." }, "matched_rule": 12, "timestamp": "2026-06-24T10:15:00+00:00" }

matched_rule is the id of the keyword rule that fired, or null if nothing matched.

Verifying signatures

Every forwarded request includes an X-TFH-Signature header: an HMAC-SHA256 of the raw request body, signed with the webhook's forward_secret. Verify it before trusting the payload:

// PHP $payload = file_get_contents('php://input'); $expected = hash_hmac('sha256', $payload, $webhookSecret); if (!hash_equals($expected, $_SERVER['HTTP_X_TFH_SIGNATURE'] ?? '')) { http_response_code(401); exit('Invalid signature'); }

Need something the API doesn't cover yet? Contact support.