> ## Documentation Index
> Fetch the complete documentation index at: https://developers.nativehub.live/llms.txt
> Use this file to discover all available pages before exploring further.

# Receive Delivery Reports

> Set up webhooks to receive real-time delivery reports and incoming messages

## 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.

<Steps>
  <Step title="Create a webhook subscription">
    Register your webhook endpoint with the events you want to receive.

    <CodeGroup>
      ```bash cURL theme={null}
      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"]
        }'
      ```

      ```javascript Node.js theme={null}
      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}`);
      ```

      ```python Python theme={null}
      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 PHP theme={null}
      <?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'];
      ```

      ```go Go theme={null}
      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)
      }
      ```

      ```java Java theme={null}
      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());
          }
      }
      ```

      ```csharp C# theme={null}
      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}");
      ```

      ```ruby Ruby theme={null}
      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']}"
      ```
    </CodeGroup>

    **Response:**

    ```json theme={null}
    {
      "id": "wh_sub_1a2b3c4d5e6f",
      "url": "https://your-domain.com/webhooks/nativemessage",
      "events": ["dlr", "mo"],
      "status": "active",
      "created_at": "2026-02-14T10:30:00Z"
    }
    ```

    <Note>
      Your webhook URL must be publicly accessible and use HTTPS in production.
    </Note>
  </Step>

  <Step title="Handle incoming DLR payloads">
    Create an endpoint to receive and process delivery report webhooks.

    **Delivery Report (DLR) Payload:**

    ```json theme={null}
    {
      "event": "dlr",
      "message_id": "msg_1a2b3c4d5e6f",
      "status": "delivered",
      "error_code": null,
      "destination": "+8801712345678",
      "delivered_at": "2026-02-14T10:30:15Z"
    }
    ```

    **Node.js Express Handler:**

    ```javascript theme={null}
    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:**

    ```python theme={null}
    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)
    ```

    <Warning>
      Always respond with HTTP 200 OK within 5 seconds. Failed webhook deliveries will be retried up to 5 times with exponential backoff.
    </Warning>
  </Step>

  <Step title="Test your webhook">
    Use the test endpoint to verify your webhook is working correctly.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST https://api-message.nativehub.live/api/v1/webhooks/subscriptions/wh_sub_1a2b3c4d5e6f/test \
        -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
      ```

      ```javascript Node.js theme={null}
      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);
      ```

      ```python Python theme={null}
      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 PHP theme={null}
      <?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;
      ```

      ```go Go theme={null}
      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)
      }
      ```

      ```java Java theme={null}
      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());
          }
      }
      ```

      ```csharp C# theme={null}
      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}");
      ```

      ```ruby Ruby theme={null}
      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}"
      ```
    </CodeGroup>

    This will send a test DLR payload to your webhook URL:

    ```json theme={null}
    {
      "event": "dlr",
      "message_id": "msg_test_1a2b3c4d5e6f",
      "status": "delivered",
      "error_code": null,
      "destination": "+8801700000000",
      "delivered_at": "2026-02-14T10:30:15Z"
    }
    ```
  </Step>
</Steps>

## Message Status Lifecycle

```mermaid theme={null}
graph LR
    A[pending] --> B[queued]
    B --> C[submitted]
    C --> D[delivered]
    C --> E[failed]
    C --> F[expired]
```

| 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.

```json theme={null}
{
  "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.

```json theme={null}
{
  "event": "mo",
  "id": "mo_1a2b3c4d5e6f",
  "from": "+8801712345678",
  "to": "BRAND",
  "body": "STOP",
  "received_at": "2026-02-14T10:30:15Z"
}
```

## Security Best Practices

<AccordionGroup>
  <Accordion title="Verify Webhook Signatures">
    Always verify the `X-Webhook-Signature` header to ensure requests are from NativeMessage.

    ```javascript theme={null}
    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...
    });
    ```
  </Accordion>

  <Accordion title="Use HTTPS">
    Always use HTTPS for webhook URLs in production to prevent man-in-the-middle attacks.
  </Accordion>

  <Accordion title="Implement Idempotency">
    Store processed webhook IDs to prevent duplicate processing during retries.

    ```python theme={null}
    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
    ```
  </Accordion>

  <Accordion title="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
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Webhook API Reference" icon="webhook" href="/api-reference/webhooks">
    Explore all webhook subscription management endpoints
  </Card>

  <Card title="Error Codes" icon="triangle-exclamation" href="/errors">
    Learn about DLR error codes and troubleshooting
  </Card>
</CardGroup>
