Skip to main content

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.
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"]
  }'
Response:
{
  "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:
{
  "event": "dlr",
  "message_id": "msg_1a2b3c4d5e6f",
  "status": "delivered",
  "error_code": null,
  "destination": "+8801712345678",
  "delivered_at": "2026-02-14T10:30:15Z"
}
Node.js Express Handler:
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');
});
Python Flask Handler:
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.
curl -X POST https://api-message.nativehub.live/api/v1/webhooks/subscriptions/wh_sub_1a2b3c4d5e6f/test \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
This will send a test DLR payload to your webhook URL:
{
  "event": "dlr",
  "message_id": "msg_test_1a2b3c4d5e6f",
  "status": "delivered",
  "error_code": null,
  "destination": "+8801700000000",
  "delivered_at": "2026-02-14T10:30:15Z"
}

Message Status Lifecycle

StatusDescription
pendingMessage accepted, awaiting queue
queuedIn queue, ready to send
submittedSent to carrier
deliveredSuccessfully delivered
failedDelivery failed
expiredDelivery 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

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...
});
Always use HTTPS for webhook URLs in production to prevent man-in-the-middle attacks.
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
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