NAV Navigation
CURL Go NodeJS PHP Python Ruby

NusaSMS API v1.0

Scroll down for code samples, example requests and responses. Select a language for code samples from the tabs above or the mobile navigation menu.

Grab your APIKey on app.nusasms.com, on Account > User management menu.

Authentication

APIKey

Get user data using API Key

GET /nusasms_api/1.0/auth/api_key

BASE_URL=https://api.nusasms.com
# BASE_URL=https://dev.nusasms.com # For testing

curl ${BASE_URL}/nusasms_api/1.0/auth/api_key \
-H "APIKey: {YOUR_APIKEY}" \
-H 'Content-Type: application/json' \
# --insecure # Ignore SSL Verification
import requests

BASE_URL = "https://api.nusasms.com/nusasms_api/1.0"
# For testing
# BASE_URL = "https://dev.nusasms.com/nusasms_api/1.0"
HEADERS = {
"Accept": "application/json",
"APIKey": "{YOUR_API_KEY}"
}

r = requests.get(
f'{BASE_URL}/auth/api_key',
headers=HEADERS,
# verify=False # Skip SSL Verification
)

print(r.json())
package main

import (
"fmt"
"io/ioutil"
"net/http"
)

func main() {
headers := map[string][]string{
"Accept": []string{"application/json"},
"APIKey": []string{" YOUR_API_KEY "},
}
url := "https://api.nusasms.com/nusasms_api/1.0/auth/api_key"
// For testing
// url := "https://dev.nusasms.com/nusasms_api/1.0/auth/api_key"

req, _ := http.NewRequest("GET", url, nil)
req.Header = headers

client := &http.Client{}
resp, err := client.Do(req)

if err != nil {
panic(err)
}

body, _ := ioutil.ReadAll(resp.Body)

fmt.Println(string(body))
// ...
}
<?php
$BASE_URL = "https://api.nusasms.com/nusasms_api/1.0/auth/api_key";

$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_HTTPHEADER => array(
'APIKey: {YOUR_API_KEY}',
'Content-Type: application/json'
),
CURLOPT_URL => $BASE_URL,

// CURLOPT_SSL_VERIFYPEER => 0, // Skip SSL Verification
));

$resp = curl_exec($curl);
echo $resp;
curl_close($curl);
const axios = require('axios');

const headers = {
'Accept':'application/json',
'APIKey':'YOUR_API_KEY'
};
const url = 'https://api.nusasms.com/nusasms_api/1.0/auth/api_key'
// Test host
//const url = 'https://dev.nusasms.com/nusasms_api/1.0/auth/api_key'

axios.get(url, {headers: headers})
.then(function(response) {
console.log(response.data)
})
.catch(error => {
if (error.response) {
console.error(error.response.data)
} else if (error.request) {
console.error(error.request)
} else {
console.error(error.message);
}
});
require 'rest-client'
require 'json'

headers = {
'Accept' => 'application/json',
'APIKey' => 'YOUR_API_KEY'
}
url = 'https://api.nusasms.com/nusasms_api/1.0/auth/api_key'
# Dev host
# url = 'https://dev.nusasms.com/nusasms_api/1.0/auth/api_key'

result = RestClient.get(url, headers=headers)

puts JSON.parse(result)

Example responses

200 Response

{
"error": false,
"message": "Data message",
"data": {
"userid": "string",
"idPerson": 0,
"idClient": 0
}
}

Responses

Status Meaning Description Schema
200 OK Successful Response PersonResponse

WhatsApp Business

Introduction

Usage flow

Rate Limit

Type of Message

Message Template

WhatsApp message templates allow businesses to use pre-approved templates to send structured messages to customers who have opted in to receive notifications.

Message template is devided in 4 parts:

Template creation limitation:

Message Status

Message status for message is sent, delivered, read, and failed.

Send Broadcast Message

POST /nusasms_api/1.0/wa_business/broadcast

package main

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)

func main() {
headers := map[string][]string {
"Content-Type": []string{"application/json"},
"Accept": []string{"application/json"},
"APIKey": []string{"YOUR_API_KEY"},
}

param_header := map[string]string {
"type": "image",
"link": "https://link.to/yourimage.png",
}
param_body := []interface{} {
map[string]string { "type": "text", "text": "your text" },
map[string]string { "type": "text", "text": "your text again" },
}
// param_buttons := []interface{} {
// map[string]interface{} {},
// }
payload, _ := json.Marshal(map[string]interface{} {
"recipient": "DESTINATION_PHONE_NUMBER",
"gateway_id": "PHONE_NUMBER_ID",
"template": map[string]string {
"name": "TEMPLATE_NAME",
"language": "TEMPLATE_LANGUAGE_CODE",
},
"header": param_header,
"body": param_body,
// "bottons": param_buttons,
})
url := "https://api.nusasms.com/nusasms_api/1.0/wa_business/broadcast"

req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
req.Header = headers

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}

body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
// ...
}
const axios = require('axios');

const headers = {
Accept: 'application/json',
APIKey: 'YOUR_API_KEY'
};
var data = {
gateway_id: "GATEWAY_ID",
recipient: "RECIPIENT_ID",
template: {
language: "LANG_CODE",
name: "TEMPLATE_NAME"
},
header: {
type: "document",
link: "https://link.to/your-document.pdf"
},
body: [
{ "type": "text", "text": "your text for param 1" },
{ "type": "text", "text": "your text for param 2" },
],
buttons: [
{
sub_type: "QUICK_REPLY",
index: 1,
parameters: {
type: "payload",
payload: "PAYLOAD"
}
}
]
}

const URL = 'https://api.nusasms.com/nusasms_api/1.0/wa_business/broadcast'

axios.post(URL, data, {headers})
.then(response => {
console.log(response.data)
})
.catch(error => {
if (error.response) {
console.error(error.response.data)
} else if (error.request) {
console.error(error.request)
} else {
console.error(error.message);
}
});
<?php

$BASE_URL = 'https://api.nusasms.com/nusasms_api/1.0/wa_business/broadcast';

$curl = curl_init();
$payload = json_encode(array(
'recipient' => 'RECIPIENT',
'gateway_id' => 'GATEWAY_ID',
'template' => array(
'language' => 'LANG_CODE',
'name' => 'TEMPLATE_NAME',
),
'header' => array(
array(
"type" => "text",
"text" => "Text for param 1 on header",
),
array(
"type" => "text",
"text" => "Text for param 2 on header",
)
)
));

echo $payload . "\n";

curl_setopt_array($curl, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $BASE_URL,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => array(
"APIKey: YOUR_API_KEY",
'Content-Type: application/json'
),
CURLOPT_POSTFIELDS => $payload,
// CURLOPT_SSL_VERIFYPEER => 0, // Skip SSL Verification
));

$resp = curl_exec($curl);
echo $resp;
import requests

BASE_URL = 'https://api.nusasms.com'
URL = f'{BASE_URL}/nusasms_api/1.0/wa_business/broadcast'
HEADERS = {"APIKey": "DEV_TESTING_API_KEY"}

PAYLOADS = {
"recipient": "RECIPIENT",
"gateway_id": "GATEWAY_ID",
"template": {
"language": "LANG_CODE",
"name": "TEMPLATE_NAME"
},
"body": [
{ "type": "text", "text": "your text for param 1" },
{ "type": "text", "text": "your text for param 2" }
],
"buttons": [
{
"sub_type": "QUICK_REPLY",
"index": 1,
"parameters": { "type": "payload", "payload": "PAYLOAD" }
}
]
}
r = requests.post(URL, json=PAYLOADS, headers=HEADERS)
print(r.json())
require 'rest-client'
require 'json'

headers = {
'APIKey' => 'DEV_TESTING_API_KEY',
'Content-Type' => 'application/json'
}
payloads = {
'recipient' => 'RECIPIENT',
'gateway_id' => 'GATEWAY_ID',
'template': {
'language' => 'LANG_CODE',
'name' => 'TEMPLATE_NAME'
},
'body' => [
{
"type" => "text",
"text" => "Text for param 1 on body",
},
{
"type" => "text",
"text" => "Text for param 2 on body",
}
]
}

base_url = 'https://api.nusasms.com/nusasms_api/1.0'

response = RestClient::Request.new({
method: :post,
url: base_url + '/wa_business/broadcast',
payload: payloads.to_json,
headers: headers
}).execute do |response, request, result|
case response.code
# Success
when 201
[ :success, puts(response.to_str) ]
when 403
[
:error,
puts("Failed status_code=#{response.code} response=#{response.to_str}")
]
when 422
[
:error,
puts("Failed status_code=#{response.code} response=#{response.to_str}")
]
else
[
:error,
puts("Failed response=#{response.to_str}")
]
end
end
URL=https://api.nusasms.com/nusasms_api/1.0/wa_business/broadcast

curl -X 'POST' $URL \
-H 'Accept: application/json' \
-H 'APIKey: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"recipient": "RECIPIENT",
"gateway_id": "GATEWAY_ID",
"template": {
"language": "LANG_CODE",
"name": "TEMPLATE_NAME"
},
"body": [
{ "type": "text", "text": "your text for param 1" },
{ "type": "text", "text": "your text for param 2" }
],
"buttons": [
{
"sub_type": "QUICK_REPLY",
"index": 1,
"parameters": { "type": "payload", "payload": "PAYLOAD" }
}
]
}'

Broadcast message can be sent using pre-approved template. A template message parameters consists of message part, namely:

Media file is provided via header. The media file types that are allowed are document, image and video. Only one media header is allowed per message.

Apart from media you can have a message title that can use multiple text parameters.

Media (image, video and document)

Text

body

You can include multiple text parameters on the body upon requesting template.

buttons

There are 2 types of button on message template

Actions

Action buttons parameter is only for dynamic URL Button. The value of the parameter (text) is developer-provided suffix that is appended to the predefined prefix URL in the template.

Quick Reply

Body parameter

{
"recipient": "string",
"gateway_id": "string",
"template": {
"name": "string",
"language": "string"
},
"header": {
"type": "image",
"link": "http://example.com",
"filename": "string"
},
"body": [
{
"type": "text",
"text": "string"
}
],
"buttons": [
{
"sub_type": "QUICK_REPLY",
"parameters": {
"type": "payload",
"payload": "string"
},
"index": 2
}
],
"message_id": "string"
}

Parameters

Name In Type Required Description
body body TemplateParam true none

Example responses

200 Response

{
"error": false,
"message": "Data message",
"data": {
"message_id": "string"
}
}

Responses

Status Meaning Description Schema
200 OK Successful Response Response
422 Unprocessable Entity Validation Error HTTPValidationError

Send Reply Message

POST /nusasms_api/1.0/wa_business/reply

package main

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)

func main() {
headers := map[string][]string {
"Content-Type": []string{"application/json"},
"Accept": []string{"application/json"},
"APIKey": []string{"YOUR_API_KEY"},
}

param_payloads := map[string]string {
"body": "YOUR TEXT MESSAGE"
}
payload, _ := json.Marshal(map[string]interface{} {
"recipient": "DESTINATION_PHONE_NUMBER",
"gateway_id": "PHONE_NUMBER_ID",
"type": "text",
"payloads": param_payloads,
})
url := "https://api.nusasms.com/nusasms_api/1.0/wa_business/reply"

req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
req.Header = headers

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}

body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
// ...
}
const axios = require('axios');

const headers = {
Accept: 'application/json',
APIKey: 'YOUR_API_KEY'
};
var data = {
gateway_id: "GATEWAY_ID",
recipient: "RECIPIENT_ID",
type: "text",
payloads: {
body: "YOUR TEXT MESSAGE HERE"
}
}

const URL = 'https://api.nusasms.com/nusasms_api/1.0/wa_business/reply'

axios.post(URL, data, {headers})
.then(response => {
console.log(response.data)
})
.catch(error => {
if (error.response) {
console.error(error.response.data)
} else if (error.request) {
console.error(error.request)
} else {
console.error(error.message);
}
});
<?php

$BASE_URL = 'https://api.nusasms.com/nusasms_api/1.0/wa_business/reply';

$curl = curl_init();
$payload = json_encode(array(
'recipient' => 'RECIPIENT',
'gateway_id' => 'GATEWAY_ID',
'type' => 'text',
'payloads' => array(
'body' => 'YOUR TEXT MESSAGE HERE'
)
));

echo $payload . "\n";

curl_setopt_array($curl, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $BASE_URL,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => array(
"APIKey: YOUR_API_KEY",
'Content-Type: application/json'
),
CURLOPT_POSTFIELDS => $payload,
// CURLOPT_SSL_VERIFYPEER => 0, // Skip SSL Verification
));

$resp = curl_exec($curl);
echo $resp;
import requests

BASE_URL = 'https://api.nusasms.com'
URL = f'{BASE_URL}/nusasms_api/1.0/wa_business/reply'
HEADERS = {"APIKey": "DEV_TESTING_API_KEY"}

PAYLOADS = {
"recipient": "RECIPIENT",
"gateway_id": "GATEWAY_ID",
"type": "text",
"payloads": {
"body": "YOUR TEXT MESSAGE HERE"
}
}
r = requests.post(URL, json=PAYLOADS, headers=HEADERS)
print(r.json())
require 'rest-client'
require 'json'

headers = {
'APIKey' => 'DEV_TESTING_API_KEY',
'Content-Type' => 'application/json'
}
payloads = {
'recipient' => 'RECIPIENT',
'gateway_id' => 'GATEWAY_ID',
'type' => 'text',
'payloads': {
'body' => 'YOUR TEXT MESSAGE HERE',
},
}

base_url = 'https://api.nusasms.com/nusasms_api/1.0'

response = RestClient::Request.new({
method: :post,
url: base_url + '/wa_business/reply',
payload: payloads.to_json,
headers: headers
}).execute do |response, request, result|
case response.code
# Success
when 201
[ :success, puts(response.to_str) ]
when 403
[
:error,
puts("Failed status_code=#{response.code} response=#{response.to_str}")
]
when 422
[
:error,
puts("Failed status_code=#{response.code} response=#{response.to_str}")
]
else
[
:error,
puts("Failed response=#{response.to_str}")
]
end
end
URL=https://api.nusasms.com/nusasms_api/1.0/wa_business/reply

curl -X 'POST' $URL \
-H 'Accept: application/json' \
-H 'APIKey: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"recipient": "RECIPIENT",
"gateway_id": "GATEWAY_ID",
"type": "text",
"payloads": {
"body": "YOUR TEXT MESSAGE HERE"
}
}'

Once your message recipients answer your Broadcast message, a conversation room created and you can send reply message. Using this Reply message you can send free text message.

Text

Image (Coming soon)

Location (Coming soon)

Body parameter

{
"recipient": "RECIPIENT",
"gateway_id": "YOUR_GATEWAY_ID",
"type": "text",
"payloads": {
"body": "text example"
}
}

Parameters

Name In Type Required Description
body body ReplyParameter true none

Example responses

200 Response

{
"error": false,
"message": "Data message",
"data": {
"message_id": "string"
}
}

Responses

Status Meaning Description Schema
200 OK Successful Response Response
422 Unprocessable Entity Validation Error HTTPValidationError

Send Otp Message

POST /nusasms_api/1.0/wa_business/otp

package main

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)

func main() {
headers := map[string][]string {
"Content-Type": []string{"application/json"},
"Accept": []string{"application/json"},
"APIKey": []string{"YOUR_API_KEY"},
}

payload, _ := json.Marshal(map[string]interface{} {
"recipient": "DESTINATION_PHONE_NUMBER",
"gateway_id": "PHONE_NUMBER_ID",
"otp": "YOUR OTP CODE",
"template": []map[string]string {
"name": "AUTHENTICATION_TEMPLATE_NAME",
"language": "TEMPLATE_LANGUAGE_CODE",
}
})
url := "https://api.nusasms.com/nusasms_api/1.0/wa_business/otp"

req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
req.Header = headers

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}

body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
// ...
}
const axios = require('axios');

const headers = {
Accept: 'application/json',
APIKey: 'YOUR_API_KEY'
};
var data = {
gateway_id: "GATEWAY_ID",
recipient: "RECIPIENT_ID",
template: {
name: "AUTH_TEMPLATE_NAME",
language: "LANG_CODE",
},
otp: "YOUR OTP CODE"
}

const URL = 'https://api.nusasms.com/nusasms_api/1.0/wa_business/otp'

axios.post(URL, data, {headers})
.then(response => {
console.log(response.data)
})
.catch(error => {
if (error.response) {
console.error(error.response.data)
} else if (error.request) {
console.error(error.request)
} else {
console.error(error.message);
}
});
<?php

$BASE_URL = 'https://api.nusasms.com/nusasms_api/1.0/wa_business/otp';

$curl = curl_init();
$payload = json_encode(array(
'recipient' => 'RECIPIENT',
'gateway_id' => 'GATEWAY_ID',
'template' => array(
'name' => 'AUTHENTICATION_TEMPLATE_NAME',
'language' => 'LANG_CODE',
),
'otp' => 'Your OTP Code',
));

echo $payload . "\n";

curl_setopt_array($curl, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $BASE_URL,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => array(
"APIKey: YOUR_API_KEY",
'Content-Type: application/json'
),
CURLOPT_POSTFIELDS => $payload,
// CURLOPT_SSL_VERIFYPEER => 0, // Skip SSL Verification
));

$resp = curl_exec($curl);
echo $resp;
import requests

BASE_URL = 'https://api.nusasms.com'
URL = f'{BASE_URL}/nusasms_api/1.0/wa_business/otp'
HEADERS = {"APIKey": "DEV_TESTING_API_KEY"}

PAYLOADS = {
"recipient": "RECIPIENT",
"gateway_id": "GATEWAY_ID",
"template": {
"name": "AUTHENTICATION_TEMPLATE_NAME",
"language": "LANG_CODE",
},
"otp": "Your OTP Code"
}
r = requests.post(URL, json=PAYLOADS, headers=HEADERS)
print(r.json())
require 'rest-client'
require 'json'

headers = {
'APIKey' => 'DEV_TESTING_API_KEY',
'Content-Type' => 'application/json'
}
payloads = {
'recipient' => 'RECIPIENT',
'gateway_id' => 'GATEWAY_ID',
'template' => {
'name' => 'AUTHENTICATION_TEMPLATE_NAME',
'language' => 'LANG_CODE',
},
'otp' => 'Your OTP Code'
}

base_url = 'https://api.nusasms.com/nusasms_api/1.0'

response = RestClient::Request.new({
method: :post,
url: base_url + '/wa_business/otp',
payload: payloads.to_json,
headers: headers
}).execute do |response, request, result|
case response.code
# Success
when 201
[ :success, puts(response.to_str) ]
when 403
[
:error,
puts("Failed status_code=#{response.code} response=#{response.to_str}")
]
when 422
[
:error,
puts("Failed status_code=#{response.code} response=#{response.to_str}")
]
else
[
:error,
puts("Failed response=#{response.to_str}")
]
end
end
URL=https://api.nusasms.com/nusasms_api/1.0/wa_business/otp

curl -X 'POST' $URL \
-H 'Accept: application/json' \
-H 'APIKey: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"recipient": "RECIPIENT",
"gateway_id": "GATEWAY_ID",
"template": {
"name": "AUTHENTICATION_TEMPLATE_NAME",
"language": "LANG_CODE"
},
"otp": "Your OTP Code"
}'

Send OTP message

OTP message can be sent if you has a Authentication Template.

OTP Text requirements

Body parameter

{
"recipient": "string",
"gateway_id": "string",
"otp": "string",
"template": {
"name": "string",
"language": "string"
}
}

Parameters

Name In Type Required Description
body body OTPParameter true none

Example responses

200 Response

{
"error": false,
"message": "Data message",
"data": {
"message_id": "string"
}
}

Responses

Status Meaning Description Schema
200 OK Successful Response Response
422 Unprocessable Entity Validation Error HTTPValidationError

Push URL Callback

package main

import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"time"
)

// Gateway represents the gateway information in the inbox payload
type Gateway struct {
ID string `json:"id"`
Number string `json:"number"`
}

// Sender represents the sender information in the inbox payload
type Sender struct {
Number string `json:"number"`
Name string `json:"name"`
}

// MessageText represents the text message content
type MessageText struct {
Body string `json:"body"`
}

// Message represents the message details in the inbox payload
type Message struct {
Type string `json:"type"`
Text MessageText `json:"text,omitempty"`
}

// Status represents the nested status object in the status payload
type Status struct {
MessageID string `json:"message_id"`
Status string `json:"status"`
Timestamp string `json:"timestamp"`
GatewayID string `json:"gateway_id,omitempty"`
Gateway *Gateway `json:"gateway,omitempty"`
Recipient string `json:"recipient,omitempty"`
}

// Inbox represents the inbox payload structure
type Inbox struct {
MessageID string `json:"message_id"`
Timestamp string `json:"timestamp"`
Gateway Gateway `json:"gateway"`
Sender Sender `json:"sender"`
Message Message `json:"message"`
}

// WebhookPayload represents the full JSON structure
type WebhookPayload struct {
Type string `json:"type"`
Status *Status `json:"status,omitempty"`
Inbox *Inbox `json:"inbox,omitempty"`
}

func handleWebhook(w http.ResponseWriter, r *http.Request) {
// Ensure only POST requests are accepted
if r.Method != http.MethodPost {
http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed)
return
}

// Limit request body size to prevent potential DoS
r.Body = http.MaxBytesReader(w, r.Body, 1048576) // 1MB max

// Read the request body
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading request body", http.StatusBadRequest)
return
}
defer r.Body.Close()

// Parse the JSON
var payload WebhookPayload
err = json.Unmarshal(body, &payload)
if err != nil {
http.Error(w, fmt.Sprintf("Error parsing JSON: %v", err), http.StatusBadRequest)
return
}

// Process the webhook based on type
processWebhook(payload)

// Respond with success
w.WriteHeader(http.StatusOK)
w.Write([]byte("Webhook received successfully"))
}

func processWebhook(payload WebhookPayload) {
fmt.Printf("Received Webhook Type: %s\n", payload.Type)

switch payload.Type {
case "status":
processStatusWebhook(payload.Status)
case "inbox":
processInboxWebhook(payload.Inbox)
default:
fmt.Printf("Unknown webhook type: %s\n", payload.Type)
}
}

func processStatusWebhook(status *Status) {
// Process the data
return nil
}

func processInboxWebhook(inbox *Inbox) {
// Process the data
return nil
}

func main() {
// Set up routes
http.HandleFunc("/webhook", handleWebhook)

// Start the server
port := 8080
fmt.Printf("Server starting on port %d...\n", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}
// webhook.js
const express = require('express');
const app = express();

app.use(express.json());

// -------------------------------------------------
// POST /webhook – accepts both payload types
// -------------------------------------------------
app.post('/webhook', (req, res) => {
const payload = req.body;

// ----- status payload -----
if (payload.type === 'status' && payload.status) {
// Process status data here
return res.json({ result: 'status processed' });
}

// ----- inbox payload -----
if (payload.type === 'inbox' && payload.inbox) {
// Process inbox data here
return res.json({ result: 'inbox processed' });
}

// ----- unknown type -----
res.status(400).json({ error: 'Unsupported payload type' });
});

// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server listening on http://localhost:${PORT}`);
});
<?php
// webhook.php
header('Content-Type: application/json');

// Read raw POST body
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);

if (json_last_error() !== JSON_ERROR_NONE) {
http_response_code(400);
echo json_encode(['error' => 'Invalid JSON']);
exit;
}

// Helper to send a JSON response
function respond(array $payload, int $code = 200): void
{
http_response_code($code);
echo json_encode($payload);
exit;
}

// Status payload
if (isset($data['type']) && $data['type'] === 'status' && isset($data['status'])) {
$status = $data['status'];

// Basic validation (you can expand as needed)
$required = ['message_id', 'status', 'timestamp', 'gateway_id', 'recipient'];
foreach ($required as $field) {
if (!isset($status[$field])) {
respond(['error' => "Missing field $field in status payload"], 400);
}
}

// Process the data here

respond(['result' => 'status processed']);
}

// Inbox payload
if (isset($data['type']) && $data['type'] === 'inbox' && isset($data['inbox'])) {
$inbox = $data['inbox'];

// Required top‑level fields
$requiredTop = ['message_id', 'timestamp', 'gateway', 'sender', 'message'];
foreach ($requiredTop as $field) {
if (!isset($inbox[$field])) {
respond(['error' => "Missing field $field in inbox payload"], 400);
}
}

// Process the data here

respond(['result' => 'inbox processed']);
}

// -----------------------------------------------------------------
// If we reach here, the payload type is unsupported
// -----------------------------------------------------------------
respond(['error' => 'Unsupported payload type'], 400);
?>
# app.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from typing import Literal, Union

app = FastAPI()


class Payload(BaseModel):
type: Literal["inbox", "status"]
inbox: dict | None
status: dict | None


# ---------- Endpoints ----------
@app.post("/webhook")
async def receive_payload(payload: Payload):
"""
Accepts either a `status` or an `inbox` payload.
"""

if payload.type == 'inbox':
# Process the data here
return {"result": "inbox processed"}
elif payload.type == 'status'
# Process the data here
return {"result": "status processed"}
raise HTTPException(status_code=400, detail="Unsupported payload type")
# webhook.rb
require 'sinatra'
require 'json'

set :port, 4567 # change if you need a different port
set :bind, '0.0.0.0'

helpers do
# tiny helper to check required keys
def has_keys?(hash, keys)
keys.all? { |k| hash.key?(k) }
end
end

post '/webhook' do
request.body.rewind
raw = request.body.read

begin
payload = JSON.parse(raw)
rescue JSON::ParserError
halt 400, { error: 'Invalid JSON' }.to_json
end

# ---------- status payload ----------
if payload['type'] == 'status' && payload['status']
status = payload['status']
# Process status data
{ result: 'status processed' }.to_json

# ---------- inbox payload ----------
elsif payload['type'] == 'inbox' && payload['inbox']
inbox = payload['inbox']
# Process inbox data
{ result: 'inbox processed' }.to_json

else
halt 400, { error: 'Unsupported payload type' }.to_json
end
end

Delivery status and Inbox data can be retrieved by setting your Callback URL. Callback URL setting can be set in Account > Account Settings > WhatsApp Business Webhook menu on app.nusasms.com.

Callback URL will be called with HTTP POST method with json content type.

Delivery Status

Delivery status is status changes of your messages. The avaiable status is

JSON Callback Example

{
  "type": "status",
  "status": {
    "message_id": "MESSAGE_ID",
    "status": "read",
    "timestamp": 1766125960,
    "gateway_id": "GATEWAY_ID",
    "recipient": "628xxxxxxx"
  }
}

Inbox

Inbox data is the message that received to your gateway from any other Whatsapp account (sender).

The message payload is the data that has been received by the gateway.

{
  "type": "inbox",
  "inbox": {
    "message_id": "MESSAGE_ID",
    "timestamp": "1766124507",
    "gateway": {
      "id": "GATEWAY_ID",
      "number": "628xxxxxxx"
    },
    "sender": {
      "number": "628yyyyyyyyy",
      "name": "Nama Akun Whatsapp Sender"
    },
    "message": {
      "type": "text",
      "text": {
        "body": "Teks inbox"
      }
    }
  }
}

Changelog

2025-04-29

2024-12-20

2023-09-15

Added

2023-07-12

Added

2023-03-03

Added

v1

Added

Schemas

Button

{
"sub_type": "QUICK_REPLY",
"parameters": {
"type": "payload",
"payload": "string"
},
"index": 2
}

Button

Properties

Name Type Required Restrictions Description
sub_type string false none none
parameters ButtonParameter true none none
index integer true none none

Enumerated Values

Property Value
sub_type QUICK_REPLY
sub_type URL

ButtonParameter

{
"type": "payload",
"payload": "string"
}

ButtonParameter

Properties

Name Type Required Restrictions Description
type string false none none
payload string false none none

Enumerated Values

Property Value
type payload

Document

{
"type": "document",
"link": "http://example.com",
"filename": "string"
}

Document

Properties

Name Type Required Restrictions Description
type string true none none
link string(uri) true none none
filename string false none none

Enumerated Values

Property Value
type document

HTTPValidationError

{
"detail": [
{
"loc": [
"string"
],
"msg": "string",
"type": "string"
}
]
}

HTTPValidationError

Properties

Name Type Required Restrictions Description
detail [ValidationError] false none none

Image

{
"type": "image",
"link": "http://example.com",
"filename": "string"
}

Image

Properties

Name Type Required Restrictions Description
type string true none none
link string(uri) true none none
filename string false none none

Enumerated Values

Property Value
type image

OTPParameter

{
"recipient": "string",
"gateway_id": "string",
"otp": "string",
"template": {
"name": "string",
"language": "string"
}
}

OTPParameter

Properties

Name Type Required Restrictions Description
recipient string true none none
gateway_id string true none none
otp string true none none
template Template true none none

Person

{
"userid": "string",
"idPerson": 0,
"idClient": 0
}

Person

Properties

Name Type Required Restrictions Description
userid string true none none
idPerson integer true none none
idClient integer true none none

PersonResponse

{
"error": false,
"message": "Data message",
"data": {
"userid": "string",
"idPerson": 0,
"idClient": 0
}
}

PersonResponse

Properties

Name Type Required Restrictions Description
error boolean false none none
message string false none none
data Person true none none

ReplyParameter

{
"recipient": "RECIPIENT",
"gateway_id": "YOUR_GATEWAY_ID",
"type": "text",
"payloads": {
"body": "text example"
}
}

ReplyParameter

Properties

Name Type Required Restrictions Description
recipient string true none none
gateway_id string true none none
type string true none none
payloads object false none none

Enumerated Values

Property Value
type text
type reaction
type audio
type document
type image
type sticker
type video
type location

Response

{
"error": false,
"message": "Data message",
"data": {
"message_id": "string"
}
}

Response

Properties

Name Type Required Restrictions Description
error boolean false none none
message string false none none
data ResponseData true none none

ResponseData

{
"message_id": "string"
}

ResponseData

Properties

Name Type Required Restrictions Description
message_id string false none none

Template

{
"name": "string",
"language": "string"
}

Template

Properties

Name Type Required Restrictions Description
name string true none none
language string true none none

TemplateParam

{
"recipient": "string",
"gateway_id": "string",
"template": {
"name": "string",
"language": "string"
},
"header": {
"type": "image",
"link": "http://example.com",
"filename": "string"
},
"body": [
{
"type": "text",
"text": "string"
}
],
"buttons": [
{
"sub_type": "QUICK_REPLY",
"parameters": {
"type": "payload",
"payload": "string"
},
"index": 2
}
],
"message_id": "string"
}

TemplateParam

Properties

Name Type Required Restrictions Description
recipient string true none none
gateway_id string true none none
template Template true none none
header any false none none

anyOf

Name Type Required Restrictions Description
» anonymous Image false none none

or

Name Type Required Restrictions Description
» anonymous Document false none none

or

Name Type Required Restrictions Description
» anonymous Video false none none

or

Name Type Required Restrictions Description
» anonymous [Text] false none none

continued

Name Type Required Restrictions Description
body [Text] false none none
buttons [Button] false none none
message_id string false none none

Text

{
"type": "text",
"text": "string"
}

Text

Properties

Name Type Required Restrictions Description
type string true none none
text string true none none

Enumerated Values

Property Value
type text

ValidationError

{
"loc": [
"string"
],
"msg": "string",
"type": "string"
}

ValidationError

Properties

Name Type Required Restrictions Description
loc [anyOf] true none none

anyOf

Name Type Required Restrictions Description
» anonymous string false none none

or

Name Type Required Restrictions Description
» anonymous integer false none none

continued

Name Type Required Restrictions Description
msg string true none none
type string true none none

Video

{
"type": "video",
"link": "http://example.com",
"filename": "string"
}

Video

Properties

Name Type Required Restrictions Description
type string true none none
link string(uri) true none none
filename string false none none

Enumerated Values

Property Value
type video