Overview
Webhooks allow you to receive real-time notifications about message delivery status (DLR) and incoming messages (MO). Instead of polling the API, your server receives HTTP POST requests when events occur.1
Create a webhook subscription
Register your webhook endpoint with the events you want to receive.Response:
curl -X POST https://api-message.nativehub.live/api/v1/webhooks/subscriptions \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-domain.com/webhooks/nativemessage",
"events": ["dlr", "mo"]
}'
const axios = require('axios');
const response = await axios.post(
'https://api-message.nativehub.live/api/v1/webhooks/subscriptions',
{
url: 'https://your-domain.com/webhooks/nativemessage',
events: ['dlr', 'mo']
},
{
headers: {
'Authorization': `Bearer ${access_token}`,
'Content-Type': 'application/json'
}
}
);
const { id, url, events, status } = response.data;
console.log(`Webhook ID: ${id}`);
import requests
response = requests.post(
'https://api-message.nativehub.live/api/v1/webhooks/subscriptions',
headers={
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
},
json={
'url': 'https://your-domain.com/webhooks/nativemessage',
'events': ['dlr', 'mo']
}
)
data = response.json()
print(f"Webhook ID: {data['id']}")
<?php
$ch = curl_init('https://api-message.nativehub.live/api/v1/webhooks/subscriptions');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'url' => 'https://your-domain.com/webhooks/nativemessage',
'events' => ['dlr', 'mo']
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $access_token
]);
$response = curl_exec($ch);
$data = json_decode($response, true);
echo "Webhook ID: " . $data['id'];
package main
import (
"bytes"
"encoding/json"
"net/http"
)
type WebhookSubscription struct {
URL string `json:"url"`
Events []string `json:"events"`
}
type WebhookResponse struct {
ID string `json:"id"`
URL string `json:"url"`
Events []string `json:"events"`
Status string `json:"status"`
}
func createWebhook(accessToken string) {
webhook, _ := json.Marshal(WebhookSubscription{
URL: "https://your-domain.com/webhooks/nativemessage",
Events: []string{"dlr", "mo"},
})
req, _ := http.NewRequest(
"POST",
"https://api-message.nativehub.live/api/v1/webhooks/subscriptions",
bytes.NewBuffer(webhook),
)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
var webhookResp WebhookResponse
json.NewDecoder(resp.Body).Decode(&webhookResp)
}
import java.net.http.*;
import java.net.URI;
import java.util.List;
import com.google.gson.Gson;
public class CreateWebhook {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
String requestBody = new Gson().toJson(Map.of(
"url", "https://your-domain.com/webhooks/nativemessage",
"events", List.of("dlr", "mo")
));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api-message.nativehub.live/api/v1/webhooks/subscriptions"))
.header("Authorization", "Bearer " + accessToken)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
using System.Net.Http;
using System.Text;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");
var content = new StringContent(
JsonSerializer.Serialize(new {
url = "https://your-domain.com/webhooks/nativemessage",
events = new[] { "dlr", "mo" }
}),
Encoding.UTF8,
"application/json"
);
var response = await client.PostAsync(
"https://api-message.nativehub.live/api/v1/webhooks/subscriptions",
content
);
var data = await response.Content.ReadFromJsonAsync<dynamic>();
Console.WriteLine($"Webhook ID: {data.id}");
require 'net/http'
require 'json'
uri = URI('https://api-message.nativehub.live/api/v1/webhooks/subscriptions')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = "Bearer #{access_token}"
request['Content-Type'] = 'application/json'
request.body = {
url: 'https://your-domain.com/webhooks/nativemessage',
events: ['dlr', 'mo']
}.to_json
response = http.request(request)
data = JSON.parse(response.body)
puts "Webhook ID: #{data['id']}"
{
"id": "wh_sub_1a2b3c4d5e6f",
"url": "https://your-domain.com/webhooks/nativemessage",
"events": ["dlr", "mo"],
"status": "active",
"created_at": "2026-02-14T10:30:00Z"
}
Your webhook URL must be publicly accessible and use HTTPS in production.
2
Handle incoming DLR payloads
Create an endpoint to receive and process delivery report webhooks.Delivery Report (DLR) Payload:Node.js Express Handler:Python Flask Handler:
{
"event": "dlr",
"message_id": "msg_1a2b3c4d5e6f",
"status": "delivered",
"error_code": null,
"destination": "+8801712345678",
"delivered_at": "2026-02-14T10:30:15Z"
}
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhooks/nativemessage', (req, res) => {
const { event, message_id, status, destination, delivered_at } = req.body;
if (event === 'dlr') {
console.log(`Message ${message_id} to ${destination}: ${status}`);
// Update your database
// await updateMessageStatus(message_id, status, delivered_at);
// Trigger notifications
if (status === 'delivered') {
// Send confirmation to user
} else if (status === 'failed') {
// Handle failure
console.log(`Error code: ${req.body.error_code}`);
}
}
// Always respond with 200 OK
res.status(200).json({ received: true });
});
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhooks/nativemessage', methods=['POST'])
def handle_webhook():
data = request.json
event = data.get('event')
if event == 'dlr':
message_id = data.get('message_id')
status = data.get('status')
destination = data.get('destination')
print(f"Message {message_id} to {destination}: {status}")
# Update database
# update_message_status(message_id, status, data.get('delivered_at'))
# Handle different statuses
if status == 'delivered':
# Success handling
pass
elif status == 'failed':
# Failure handling
error_code = data.get('error_code')
print(f"Error code: {error_code}")
# Always return 200 OK
return jsonify({'received': True}), 200
if __name__ == '__main__':
app.run(port=3000)
Always respond with HTTP 200 OK within 5 seconds. Failed webhook deliveries will be retried up to 5 times with exponential backoff.
3
Test your webhook
Use the test endpoint to verify your webhook is working correctly.This will send a test DLR payload to your webhook URL:
curl -X POST https://api-message.nativehub.live/api/v1/webhooks/subscriptions/wh_sub_1a2b3c4d5e6f/test \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
const axios = require('axios');
const response = await axios.post(
'https://api-message.nativehub.live/api/v1/webhooks/subscriptions/wh_sub_1a2b3c4d5e6f/test',
{},
{
headers: {
'Authorization': `Bearer ${access_token}`
}
}
);
console.log('Test webhook sent:', response.data);
import requests
response = requests.post(
'https://api-message.nativehub.live/api/v1/webhooks/subscriptions/wh_sub_1a2b3c4d5e6f/test',
headers={
'Authorization': f'Bearer {access_token}'
}
)
print('Test webhook sent:', response.json())
<?php
$ch = curl_init('https://api-message.nativehub.live/api/v1/webhooks/subscriptions/wh_sub_1a2b3c4d5e6f/test');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $access_token
]);
$response = curl_exec($ch);
echo "Test webhook sent: " . $response;
package main
import (
"encoding/json"
"net/http"
)
func testWebhook(accessToken, webhookID string) {
req, _ := http.NewRequest(
"POST",
"https://api-message.nativehub.live/api/v1/webhooks/subscriptions/"+webhookID+"/test",
nil,
)
req.Header.Set("Authorization", "Bearer "+accessToken)
client := &http.Client{}
resp, _ := client.Do(req)
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
}
import java.net.http.*;
import java.net.URI;
public class TestWebhook {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api-message.nativehub.live/api/v1/webhooks/subscriptions/wh_sub_1a2b3c4d5e6f/test"))
.header("Authorization", "Bearer " + accessToken)
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("Test webhook sent: " + response.body());
}
}
using System.Net.Http;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");
var response = await client.PostAsync(
"https://api-message.nativehub.live/api/v1/webhooks/subscriptions/wh_sub_1a2b3c4d5e6f/test",
null
);
var data = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Test webhook sent: {data}");
require 'net/http'
require 'json'
uri = URI('https://api-message.nativehub.live/api/v1/webhooks/subscriptions/wh_sub_1a2b3c4d5e6f/test')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = "Bearer #{access_token}"
response = http.request(request)
puts "Test webhook sent: #{response.body}"
{
"event": "dlr",
"message_id": "msg_test_1a2b3c4d5e6f",
"status": "delivered",
"error_code": null,
"destination": "+8801700000000",
"delivered_at": "2026-02-14T10:30:15Z"
}
Message Status Lifecycle
| Status | Description |
|---|---|
pending | Message accepted, awaiting queue |
queued | In queue, ready to send |
submitted | Sent to carrier |
delivered | Successfully delivered |
failed | Delivery failed |
expired | Delivery timeout exceeded |
Webhook Event Types
Delivery Report (DLR)
Sent when a message status changes.{
"event": "dlr",
"message_id": "msg_1a2b3c4d5e6f",
"status": "delivered",
"error_code": null,
"destination": "+8801712345678",
"delivered_at": "2026-02-14T10:30:15Z"
}
Mobile Originated (MO)
Sent when you receive an incoming message.{
"event": "mo",
"id": "mo_1a2b3c4d5e6f",
"from": "+8801712345678",
"to": "BRAND",
"body": "STOP",
"received_at": "2026-02-14T10:30:15Z"
}
Security Best Practices
Verify Webhook Signatures
Verify Webhook Signatures
Always verify the
X-Webhook-Signature header to ensure requests are from NativeMessage.const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
const digest = hmac.update(JSON.stringify(payload)).digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
}
app.post('/webhooks/nativemessage', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const isValid = verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook...
});
Use HTTPS
Use HTTPS
Always use HTTPS for webhook URLs in production to prevent man-in-the-middle attacks.
Implement Idempotency
Implement Idempotency
Store processed webhook IDs to prevent duplicate processing during retries.
processed_webhooks = set()
@app.route('/webhooks/nativemessage', methods=['POST'])
def handle_webhook():
webhook_id = request.headers.get('X-Webhook-ID')
if webhook_id in processed_webhooks:
return jsonify({'received': True}), 200
# Process webhook...
processed_webhooks.add(webhook_id)
return jsonify({'received': True}), 200
Handle Retries Gracefully
Handle Retries Gracefully
NativeMessage retries failed webhooks up to 5 times with exponential backoff:
- 1st retry: 1 minute
- 2nd retry: 5 minutes
- 3rd retry: 15 minutes
- 4th retry: 1 hour
- 5th retry: 6 hours
Next Steps
Webhook API Reference
Explore all webhook subscription management endpoints
Error Codes
Learn about DLR error codes and troubleshooting