curl --request POST \
--url https://api.orbit.devotel.io/api/v1/messages/whatsapp \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"to": "<string>",
"from": "<string>",
"template_params": {},
"coupon": {
"prefix": "<string>",
"length": 14,
"seed": "<string>",
"alphabet": "<string>"
},
"webhook_url": "<string>",
"metadata": {},
"scheduled_at": "<string>",
"conversation_id": "<string>",
"contact_id": "<string>"
}
'import requests
url = "https://api.orbit.devotel.io/api/v1/messages/whatsapp"
payload = {
"to": "<string>",
"from": "<string>",
"template_params": {},
"coupon": {
"prefix": "<string>",
"length": 14,
"seed": "<string>",
"alphabet": "<string>"
},
"webhook_url": "<string>",
"metadata": {},
"scheduled_at": "<string>",
"conversation_id": "<string>",
"contact_id": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
to: '<string>',
from: '<string>',
template_params: {},
coupon: {prefix: '<string>', length: 14, seed: '<string>', alphabet: '<string>'},
webhook_url: '<string>',
metadata: {},
scheduled_at: '<string>',
conversation_id: '<string>',
contact_id: '<string>'
})
};
fetch('https://api.orbit.devotel.io/api/v1/messages/whatsapp', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.orbit.devotel.io/api/v1/messages/whatsapp",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'to' => '<string>',
'from' => '<string>',
'template_params' => [
],
'coupon' => [
'prefix' => '<string>',
'length' => 14,
'seed' => '<string>',
'alphabet' => '<string>'
],
'webhook_url' => '<string>',
'metadata' => [
],
'scheduled_at' => '<string>',
'conversation_id' => '<string>',
'contact_id' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.orbit.devotel.io/api/v1/messages/whatsapp"
payload := strings.NewReader("{\n \"to\": \"<string>\",\n \"from\": \"<string>\",\n \"template_params\": {},\n \"coupon\": {\n \"prefix\": \"<string>\",\n \"length\": 14,\n \"seed\": \"<string>\",\n \"alphabet\": \"<string>\"\n },\n \"webhook_url\": \"<string>\",\n \"metadata\": {},\n \"scheduled_at\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"contact_id\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.orbit.devotel.io/api/v1/messages/whatsapp")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"to\": \"<string>\",\n \"from\": \"<string>\",\n \"template_params\": {},\n \"coupon\": {\n \"prefix\": \"<string>\",\n \"length\": 14,\n \"seed\": \"<string>\",\n \"alphabet\": \"<string>\"\n },\n \"webhook_url\": \"<string>\",\n \"metadata\": {},\n \"scheduled_at\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"contact_id\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.orbit.devotel.io/api/v1/messages/whatsapp")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"to\": \"<string>\",\n \"from\": \"<string>\",\n \"template_params\": {},\n \"coupon\": {\n \"prefix\": \"<string>\",\n \"length\": 14,\n \"seed\": \"<string>\",\n \"alphabet\": \"<string>\"\n },\n \"webhook_url\": \"<string>\",\n \"metadata\": {},\n \"scheduled_at\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"contact_id\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "<string>",
"status": "<string>",
"channel": "whatsapp",
"from": "<string>",
"to": "<string>",
"body": "<string>",
"price": "<string>",
"currency": "<string>",
"external_id": "<string>",
"created_at": "2023-11-07T05:31:56Z"
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "RATE_LIMITED",
"message": "<string>",
"status": 429,
"retry_after": 2
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}Send a WhatsApp message
Send a WhatsApp text, template, or media message. Outside Meta’s 24h re-engagement window only template type is permitted; the API rejects freeform text/media with 422 in that case.
curl --request POST \
--url https://api.orbit.devotel.io/api/v1/messages/whatsapp \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"to": "<string>",
"from": "<string>",
"template_params": {},
"coupon": {
"prefix": "<string>",
"length": 14,
"seed": "<string>",
"alphabet": "<string>"
},
"webhook_url": "<string>",
"metadata": {},
"scheduled_at": "<string>",
"conversation_id": "<string>",
"contact_id": "<string>"
}
'import requests
url = "https://api.orbit.devotel.io/api/v1/messages/whatsapp"
payload = {
"to": "<string>",
"from": "<string>",
"template_params": {},
"coupon": {
"prefix": "<string>",
"length": 14,
"seed": "<string>",
"alphabet": "<string>"
},
"webhook_url": "<string>",
"metadata": {},
"scheduled_at": "<string>",
"conversation_id": "<string>",
"contact_id": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
to: '<string>',
from: '<string>',
template_params: {},
coupon: {prefix: '<string>', length: 14, seed: '<string>', alphabet: '<string>'},
webhook_url: '<string>',
metadata: {},
scheduled_at: '<string>',
conversation_id: '<string>',
contact_id: '<string>'
})
};
fetch('https://api.orbit.devotel.io/api/v1/messages/whatsapp', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.orbit.devotel.io/api/v1/messages/whatsapp",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'to' => '<string>',
'from' => '<string>',
'template_params' => [
],
'coupon' => [
'prefix' => '<string>',
'length' => 14,
'seed' => '<string>',
'alphabet' => '<string>'
],
'webhook_url' => '<string>',
'metadata' => [
],
'scheduled_at' => '<string>',
'conversation_id' => '<string>',
'contact_id' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.orbit.devotel.io/api/v1/messages/whatsapp"
payload := strings.NewReader("{\n \"to\": \"<string>\",\n \"from\": \"<string>\",\n \"template_params\": {},\n \"coupon\": {\n \"prefix\": \"<string>\",\n \"length\": 14,\n \"seed\": \"<string>\",\n \"alphabet\": \"<string>\"\n },\n \"webhook_url\": \"<string>\",\n \"metadata\": {},\n \"scheduled_at\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"contact_id\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.orbit.devotel.io/api/v1/messages/whatsapp")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"to\": \"<string>\",\n \"from\": \"<string>\",\n \"template_params\": {},\n \"coupon\": {\n \"prefix\": \"<string>\",\n \"length\": 14,\n \"seed\": \"<string>\",\n \"alphabet\": \"<string>\"\n },\n \"webhook_url\": \"<string>\",\n \"metadata\": {},\n \"scheduled_at\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"contact_id\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.orbit.devotel.io/api/v1/messages/whatsapp")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"to\": \"<string>\",\n \"from\": \"<string>\",\n \"template_params\": {},\n \"coupon\": {\n \"prefix\": \"<string>\",\n \"length\": 14,\n \"seed\": \"<string>\",\n \"alphabet\": \"<string>\"\n },\n \"webhook_url\": \"<string>\",\n \"metadata\": {},\n \"scheduled_at\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"contact_id\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "<string>",
"status": "<string>",
"channel": "whatsapp",
"from": "<string>",
"to": "<string>",
"body": "<string>",
"price": "<string>",
"currency": "<string>",
"external_id": "<string>",
"created_at": "2023-11-07T05:31:56Z"
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "RATE_LIMITED",
"message": "<string>",
"status": 429,
"retry_after": 2
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}Authorizations
Dashboard JWT token from Clerk
Headers
Stripe-style idempotency token. Pass a stable, client-generated value (1-255 chars) to dedupe retries on transient timeouts. The same key+credential+path replays the original response for 24h on 2xx (5min on 4xx, 30s on 5xx). Returns 409 if a concurrent request with the same key is already in flight; replayed responses include the Idempotency-Replay: true response header.
1 - 255Sandbox opt-in for Clerk-session-authenticated requests. Set to true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.
true, false Body
E.164 phone number OR WhatsApp Business Solution UID (5-130 alphanumeric chars).
Message kind. text and the media kinds (image/document/audio/video/location/contact) are only permitted inside Meta's 24h customer-service window; outside it, use template with an approved template or the send is rejected with 422.
text, template, image, document, audio, video, location, contact, interactive, reaction Sender E.164 (overrides default WABA number).
1 - 64Freeform text body — required when type is text. Only permitted inside Meta's 24h customer-service window.
Show child attributes
Show child attributes
Interactive payload — required when type is interactive (list / buttons / cta_url / flow / product / product_list).
Show child attributes
Show child attributes
Reaction payload — required when type is reaction. References an earlier outbound message by Meta id and carries a single emoji.
Show child attributes
Show child attributes
Approved template to send — required when type is template. language.code MUST match the locale the template was approved under in WhatsApp Manager (e.g. en_US, en_GB, es_ES, pt_BR), or the send is rejected with 422 WHATSAPP_TEMPLATE_NOT_FOUND (Meta 132001). Supply variables via either components (Meta-native) or the flat template_params sibling field below — not both.
Show child attributes
Show child attributes
Flat string→string map filling the template's positional {{N}} placeholders (keys '1'..'N'). Alternative to the Meta-native template.components shape.
Show child attributes
Show child attributes
Optional Limited-Time-Offer coupon config — when supplied, a deterministic per-recipient coupon code is generated and attached to the send's paired COPY_CODE button. Skip this and set metadata.lto_coupon_code directly to supply a literal code instead.
Show child attributes
Show child attributes
Media payload — required when type is image, document, audio, or video. url must be a signed HTTPS URL (e.g. one returned by POST /api/v1/files/upload).
Show child attributes
Show child attributes
DLR callback URL (HTTPS). Optional — an empty string is treated as 'no webhook'. Non-empty values are validated server-side: must be https:// and pass the SSRF guard.
2048Arbitrary string→string metadata, echoed back on delivery-status webhooks and message lookups.
Show child attributes
Show child attributes
ISO 8601 timestamp to schedule delivery instead of sending immediately (e.g. "2026-08-01T15:00:00Z"); a datetime-local value without a timezone suffix is also accepted.
Open thread's conversation id — scopes the 24h customer-service window pre-flight to the same inbound match the composer uses. Does not re-thread the outbound message.
100Resolved contact id for the recipient — scopes the 24h customer-service window pre-flight (parity with the composer's window-status probe).
100