Incoming SMS Webhooks

Receive real-time notifications when SMS messages are received on your channels.

Overview

When an SMS is received on your channel, the platform will call your configured webhook endpoint, allowing you to process the message and optionally return an auto-reply.

✅ New: HTTP POST (JSON) method now available! Infobip-compatible format with HMAC-SHA256 signatures.

Webhook Methods

Choose the method that best fits your integration:

Method Format Security Best For
POST (JSON) JSON payload HMAC-SHA256 Modern APIs, Infobip migration
GET Query parameters Security key + Base64 Simple integrations, legacy systems
SOAP SOAP 1.2 XML Security key in body Enterprise systems, .NET SOAP

HTTP POST (JSON) Method

Recommended for new integrations. Modern JSON format with HMAC-SHA256 signatures.

POST https://your-api.com/webhooks/incoming-sms

Request Headers

Content-Type: application/json
X-Signature: sha256=<hmac-sha256-signature>
User-Agent: SMSDesk-Webhook/1.0

Request Payload

{
  "type": "incoming_sms",
  "timestamp": "2026-07-11T07:22:00.0000000Z",
  "data": {
    "messageId": "123456789",
    "from": "+264812182326",
    "to": "44880",
    "text": "Hello, this is a test message",
    "receivedAt": "2026-07-11T07:22:00.0000000Z",
    "clientId": 1059
  }
}

Payload Fields

Field Type Description
type string Always "incoming_sms"
timestamp string (ISO 8601) UTC timestamp when webhook was sent
data.messageId string Internal message ID
data.from string Sender mobile number (international format)
data.to string Recipient channel/shortcode
data.text string Message content
data.receivedAt string (ISO 8601) UTC timestamp when message was received
data.clientId integer Your client ID

Response (Auto-Reply)

Your endpoint can return a plain text response that will be sent as an auto-reply SMS:

HTTP/1.1 200 OK
Content-Type: text/plain

Thank you for your message. We will respond shortly.

Return empty body to use the default auto-response configured in the portal:

HTTP/1.1 200 OK
Content-Type: text/plain

HTTP GET Method

Legacy method using query parameters.

GET https://your-api.com/webhook/incoming?securitykey=...&sender=...&message=...

Query Parameters

Parameter Type Description
securitykey string Your configured security key
incomingsmsid long Unique message ID
timestamp long Unix timestamp (seconds)
sender string Sender mobile number
recipient string Your channel/shortcode
msg string Base64-encoded message wrapped with security key
message string Plain text message content

Security

The msg parameter contains the message wrapped with your security key and Base64-encoded:

  1. Original message: Hello
  2. Wrapped: <YOUR_KEY>Hello<YOUR_KEY>
  3. Base64 encoded: PFlPVVJfS0VZPkhlbGxvPFlPVVJfS0VZPg==

SOAP Method

SOAP 1.2 XML envelope for enterprise integrations.

POST https://your-api.com/soap/incoming

SOAP Request

<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                 xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
  <soap12:Body>
    <incomingsms xmlns="http://tempuri.org/">
      <incomingsmsid>12345</incomingsmsid>
      <timestamp>1720522800</timestamp>
      <sender>+264811234567</sender>
      <msg>PFlPVVJfS0VZPkhlbGxvPFlPVVJfS0VZPg==</msg>
      <securitykey>YOUR_KEY</securitykey>
    </incomingsms>
  </soap12:Body>
</soap12:Envelope>

Configuration

Web Portal Setup

  1. Log in to your SMSDESK portal (e.g., https://desk.sms.com.na for Namibia deployment)
  2. Navigate to Account → SMS Channels
  3. Click on your channel to edit
  4. In Webhooks section, configure:
    • Incoming SMS Handler: Select POST (JSON), GET, or SOAP
    • WebService URL: Your webhook endpoint
    • Security Key: Your secret (use "Generate" button for POST)
  5. Save changes

Code Examples

Python / Flask

from flask import Flask, request, jsonify
import hmac
import hashlib

app = Flask(__name__)
SECRET = 'your-secret-here'

@app.route('/webhooks/incoming-sms', methods=['POST'])
def incoming_sms():
    # Get raw body
    payload = request.get_data(as_text=True)
    
    # Verify signature
    signature = request.headers.get('X-Signature', '')
    if not verify_signature(payload, SECRET, signature):
        return jsonify({'error': 'Invalid signature'}), 401
    
    # Parse JSON
    webhook = request.get_json()
    process_message(webhook['data'])
    
    # Return auto-reply (optional)
    return 'Thank you for your message!', 200

def verify_signature(payload, secret, sig):
    if not sig or not sig.startswith('sha256='):
        return False
    
    expected = hmac.new(
        secret.encode('utf-8'),
        payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    
    received = sig[7:]  # Remove 'sha256=' prefix
    return hmac.compare_digest(expected, received)

def process_message(data):
    print(f"Received SMS from {data['from']}: {data['text']}")
    # Add your processing logic here

if __name__ == '__main__':
    app.run(port=3000)

JavaScript / Node.js / Express

const express = require('express');
const crypto = require('crypto');
const app = express();

const SECRET = 'your-secret-here';

app.post('/webhooks/incoming-sms', 
    express.text({ type: 'application/json' }), 
    (req, res) => {
        // Verify signature
        const signature = req.headers['x-signature'];
        if (!verifySignature(req.body, SECRET, signature)) {
            return res.status(401).json({ error: 'Invalid signature' });
        }

        // Parse and process
        const webhook = JSON.parse(req.body);
        processMessage(webhook.data);

        // Return auto-reply (optional)
        res.send('Thank you for your message!');
    }
);

function verifySignature(payload, secret, sig) {
    if (!sig || !sig.startsWith('sha256=')) return false;
    
    const hmac = crypto.createHmac('sha256', secret);
    hmac.update(payload, 'utf8');
    const expected = hmac.digest('hex');
    
    return crypto.timingSafeEqual(
        Buffer.from(sig.substring(7)),
        Buffer.from(expected)
    );
}

function processMessage(data) {
    console.log(`Received SMS from ${data.from}: ${data.text}`);
    // Add your processing logic here
}

app.listen(3000);

C# / ASP.NET Core

using Microsoft.AspNetCore.Mvc;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

[ApiController]
[Route("webhooks/incoming-sms")]
public class IncomingSmsController : ControllerBase
{
    private readonly string _secret = "your-secret-here";

    [HttpPost]
    public async Task<IActionResult> Receive()
    {
        using var reader = new StreamReader(Request.Body);
        string payload = await reader.ReadToEndAsync();

        // Verify signature
        string signature = Request.Headers["X-Signature"];
        if (!VerifySignature(payload, _secret, signature))
            return Unauthorized(new { error = "Invalid signature" });

        // Parse and process
        var webhook = JsonSerializer.Deserialize<IncomingSmsWebhook>(payload);
        ProcessMessage(webhook.Data);

        // Return auto-reply (optional)
        return Ok("Thank you for your message!");
    }

    private bool VerifySignature(string payload, string secret, string sig)
    {
        if (string.IsNullOrEmpty(sig) || !sig.StartsWith("sha256="))
            return false;

        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
        var expected = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
        
        return sig.Substring(7) == expected;
    }

    private void ProcessMessage(IncomingSmsData data)
    {
        Console.WriteLine($"Received SMS from {data.From}: {data.Text}");
        // Add your processing logic here
    }
}

PHP

<?php
$secret = 'your-secret-here';

// Get raw POST body
$payload = file_get_contents('php://input');

// Get signature header
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';

// Verify signature
if (!verifySignature($payload, $secret, $signature)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

// Parse JSON
$webhook = json_decode($payload, true);
processMessage($webhook['data']);

// Return auto-reply (optional)
http_response_code(200);
echo 'Thank you for your message!';

function verifySignature($payload, $secret, $sig) {
    if (empty($sig) || strpos($sig, 'sha256=') !== 0) {
        return false;
    }
    
    $expected = hash_hmac('sha256', $payload, $secret);
    $received = substr($sig, 7); // Remove 'sha256=' prefix
    
    return hash_equals($expected, $received);
}

function processMessage($data) {
    error_log("Received SMS from {$data['from']}: {$data['text']}");
    // Add your processing logic here
}
?>

Java / Spring Boot

import org.springframework.web.bind.annotation.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

@RestController
@RequestMapping("/webhooks/incoming-sms")
public class IncomingSmsController {
    
    private static final String SECRET = "your-secret-here";
    
    @PostMapping
    public ResponseEntity<String> receiveIncomingSms(
            @RequestBody String payload,
            @RequestHeader("X-Signature") String signature) {
        
        // Verify signature
        if (!verifySignature(payload, SECRET, signature)) {
            return ResponseEntity.status(401)
                .body("{\"error\": \"Invalid signature\"}");
        }
        
        // Parse JSON
        ObjectMapper mapper = new ObjectMapper();
        IncomingSmsWebhook webhook = mapper.readValue(payload, IncomingSmsWebhook.class);
        processMessage(webhook.getData());
        
        // Return auto-reply (optional)
        return ResponseEntity.ok("Thank you for your message!");
    }
    
    private boolean verifySignature(String payload, String secret, String sig) {
        if (sig == null || !sig.startsWith("sha256=")) {
            return false;
        }
        
        try {
            Mac hmac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(
                secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            hmac.init(secretKey);
            
            byte[] hash = hmac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
            String expected = bytesToHex(hash);
            String received = sig.substring(7);
            
            return MessageDigest.isEqual(
                expected.getBytes(StandardCharsets.UTF_8),
                received.getBytes(StandardCharsets.UTF_8)
            );
        } catch (Exception e) {
            return false;
        }
    }
    
    private String bytesToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02x", b));
        }
        return result.toString();
    }
    
    private void processMessage(IncomingSmsData data) {
        System.out.println("Received SMS from " + data.getFrom() + ": " + data.getText());
        // Add your processing logic here
    }
}
Ready to Start? Configure your webhook in your SMSDESK portal and start receiving incoming SMS messages!