Skip to content

Webhook Setup ​

Receive real-time notifications when message events occur using webhooks.

Overview ​

Webhooks allow your application to receive instant notifications when events happen, such as:

  • Message delivered
  • Message failed
  • Message sent
  • Message status changed

Instead of polling the API for status updates, YeboLink will send HTTP POST requests to your webhook URL.

Create a Webhook ​

Endpoint ​

POST /api/v1/webhooks

Authentication ​

Requires JWT token via Authorization: Bearer header.

Request Body ​

ParameterTypeRequiredDescription
urlstringYesYour webhook endpoint URL (must be HTTPS)
eventsarrayYesArray of event types to subscribe to

Available Events ​

  • message.sent - Message sent to provider
  • message.delivered - Message delivered to recipient
  • message.failed - Message delivery failed
  • message.queued - Message queued for sending

Example Requests ​

bash
curl -X POST https://api.yebolink.com/api/v1/webhooks \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -d '{
    "url": "https://your-app.com/webhooks/yebolink",
    "events": ["message.sent", "message.delivered", "message.failed"]
  }'
javascript
const createWebhook = async (url, events) => {
  const response = await fetch('https://api.yebolink.com/api/v1/webhooks', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${jwtToken}`
    },
    body: JSON.stringify({ url, events })
  });

  const data = await response.json();

  // IMPORTANT: Save the secret securely
  console.log('Webhook Secret:', data.data.secret);
  console.log('Store this secret - it will not be shown again!');

  return data;
};

// Usage
const webhook = await createWebhook(
  'https://your-app.com/webhooks/yebolink',
  ['message.sent', 'message.delivered', 'message.failed']
);
python
import requests

def create_webhook(jwt_token: str, url: str, events: list):
    response = requests.post(
        'https://api.yebolink.com/api/v1/webhooks',
        headers={'Authorization': f'Bearer {jwt_token}'},
        json={'url': url, 'events': events}
    )

    data = response.json()

    # IMPORTANT: Save the secret securely
    print(f"Webhook Secret: {data['data']['secret']}")
    print("Store this secret - it will not be shown again!")

    return data

# Usage
webhook = create_webhook(
    jwt_token,
    'https://your-app.com/webhooks/yebolink',
    ['message.sent', 'message.delivered', 'message.failed']
)
php
<?php
function createWebhook($jwtToken, $url, $events) {
    $curl = curl_init();

    curl_setopt_array($curl, [
        CURLOPT_URL => 'https://api.yebolink.com/api/v1/webhooks',
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            'Content-Type: application/json',
            "Authorization: Bearer $jwtToken"
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'url' => $url,
            'events' => $events
        ])
    ]);

    $response = curl_exec($curl);
    $data = json_decode($response, true);

    // IMPORTANT: Save the secret securely
    echo "Webhook Secret: " . $data['data']['secret'] . "\n";
    echo "Store this secret - it will not be shown again!\n";

    curl_close($curl);
    return $data;
}

// Usage
$webhook = createWebhook(
    $jwtToken,
    'https://your-app.com/webhooks/yebolink',
    ['message.sent', 'message.delivered', 'message.failed']
);
?>

Response ​

json
{
  "success": true,
  "data": {
    "webhook": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "url": "https://your-app.com/webhooks/yebolink",
      "events": ["message.sent", "message.delivered", "message.failed"],
      "is_active": true,
      "created_at": "2025-11-02T12:00:00Z"
    },
    "secret": "whsec_abc123def456ghi789jkl012mno345pqr678stu901vwx234yz",
    "warning": "Save this secret securely. It will not be shown again."
  }
}

Save Your Secret

The webhook secret is only shown once during creation. Store it securely in your environment variables. You'll need it to verify webhook signatures.

Receiving Webhook Events ​

Your webhook endpoint should accept HTTP POST requests and return a 200 status code to acknowledge receipt.

Webhook Payload Format ​

json
{
  "event": "message.delivered",
  "timestamp": "2025-11-02T12:00:00Z",
  "data": {
    "message_id": "550e8400-e29b-41d4-a716-446655440000",
    "channel": "sms",
    "recipient": "+1234567890",
    "status": "delivered",
    "credits_used": 1,
    "metadata": {
      "user_id": "12345"
    }
  }
}

Webhook Headers ​

YeboLink sends these headers with each webhook:

  • X-YeboLink-Signature: HMAC SHA256 signature for verification
  • X-YeboLink-Event: Event type (e.g., message.delivered)
  • Content-Type: application/json

Implementing Webhook Handler ​

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

const app = express();

// IMPORTANT: Use express.raw() for webhook routes to preserve raw body
app.use('/webhooks/yebolink', express.raw({ type: 'application/json' }));

// Other routes can use express.json()
app.use(express.json());

const WEBHOOK_SECRET = process.env.YEBOLINK_WEBHOOK_SECRET;

app.post('/webhooks/yebolink', (req, res) => {
  const signature = req.headers['x-yebolink-signature'];
  const event = req.headers['x-yebolink-event'];

  // Verify signature
  const isValid = verifySignature(req.body, signature, WEBHOOK_SECRET);

  if (!isValid) {
    console.error('Invalid webhook signature');
    return res.status(403).send('Invalid signature');
  }

  // Parse payload
  const payload = JSON.parse(req.body);

  // Handle event
  handleWebhookEvent(event, payload.data);

  // Acknowledge receipt
  res.status(200).send('OK');
});

function verifySignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

function handleWebhookEvent(event, data) {
  switch (event) {
    case 'message.sent':
      console.log(`Message ${data.message_id} sent to ${data.recipient}`);
      break;

    case 'message.delivered':
      console.log(`Message ${data.message_id} delivered to ${data.recipient}`);
      // Update your database
      updateMessageStatus(data.message_id, 'delivered');
      break;

    case 'message.failed':
      console.log(`Message ${data.message_id} failed: ${data.error_message}`);
      // Handle failure
      logMessageFailure(data.message_id, data.error_message);
      break;

    default:
      console.log(`Unknown event: ${event}`);
  }
}

app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});
python
from flask import Flask, request, Response
import hmac
import hashlib
import json
import os

app = Flask(__name__)

WEBHOOK_SECRET = os.environ.get('YEBOLINK_WEBHOOK_SECRET')

@app.route('/webhooks/yebolink', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-YeboLink-Signature')
    event = request.headers.get('X-YeboLink-Event')

    # Get raw body
    payload = request.get_data()

    # Verify signature
    if not verify_signature(payload, signature, WEBHOOK_SECRET):
        print('Invalid webhook signature')
        return Response('Invalid signature', status=403)

    # Parse payload
    data = json.loads(payload)

    # Handle event
    handle_event(event, data['data'])

    # Acknowledge receipt
    return Response('OK', status=200)

def verify_signature(payload, signature, secret):
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected)

def handle_event(event, data):
    if event == 'message.sent':
        print(f"Message {data['message_id']} sent to {data['recipient']}")

    elif event == 'message.delivered':
        print(f"Message {data['message_id']} delivered to {data['recipient']}")
        # Update your database
        update_message_status(data['message_id'], 'delivered')

    elif event == 'message.failed':
        print(f"Message {data['message_id']} failed: {data.get('error_message')}")
        # Handle failure
        log_message_failure(data['message_id'], data.get('error_message'))

    else:
        print(f"Unknown event: {event}")

if __name__ == '__main__':
    app.run(port=3000)
php
<?php
// webhook_handler.php

$webhookSecret = getenv('YEBOLINK_WEBHOOK_SECRET');

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

// Get headers
$signature = $_SERVER['HTTP_X_YEBOLINK_SIGNATURE'] ?? '';
$event = $_SERVER['HTTP_X_YEBOLINK_EVENT'] ?? '';

// Verify signature
if (!verifySignature($payload, $signature, $webhookSecret)) {
    http_response_code(403);
    die('Invalid signature');
}

// Parse payload
$data = json_decode($payload, true);

// Handle event
handleEvent($event, $data['data']);

// Acknowledge receipt
http_response_code(200);
echo 'OK';

function verifySignature($payload, $signature, $secret) {
    $expected = hash_hmac('sha256', $payload, $secret);
    return hash_equals($signature, $expected);
}

function handleEvent($event, $data) {
    switch ($event) {
        case 'message.sent':
            error_log("Message {$data['message_id']} sent to {$data['recipient']}");
            break;

        case 'message.delivered':
            error_log("Message {$data['message_id']} delivered to {$data['recipient']}");
            // Update your database
            updateMessageStatus($data['message_id'], 'delivered');
            break;

        case 'message.failed':
            error_log("Message {$data['message_id']} failed: " . ($data['error_message'] ?? ''));
            // Handle failure
            logMessageFailure($data['message_id'], $data['error_message'] ?? '');
            break;

        default:
            error_log("Unknown event: $event");
    }
}
?>
ruby
require 'sinatra'
require 'json'
require 'openssl'

WEBHOOK_SECRET = ENV['YEBOLINK_WEBHOOK_SECRET']

post '/webhooks/yebolink' do
  signature = request.env['HTTP_X_YEBOLINK_SIGNATURE']
  event = request.env['HTTP_X_YEBOLINK_EVENT']

  # Get raw body
  request.body.rewind
  payload = request.body.read

  # Verify signature
  unless verify_signature(payload, signature, WEBHOOK_SECRET)
    puts 'Invalid webhook signature'
    halt 403, 'Invalid signature'
  end

  # Parse payload
  data = JSON.parse(payload)

  # Handle event
  handle_event(event, data['data'])

  # Acknowledge receipt
  status 200
  'OK'
end

def verify_signature(payload, signature, secret)
  expected = OpenSSL::HMAC.hexdigest('SHA256', secret, payload)
  Rack::Utils.secure_compare(signature, expected)
end

def handle_event(event, data)
  case event
  when 'message.sent'
    puts "Message #{data['message_id']} sent to #{data['recipient']}"

  when 'message.delivered'
    puts "Message #{data['message_id']} delivered to #{data['recipient']}"
    # Update your database
    update_message_status(data['message_id'], 'delivered')

  when 'message.failed'
    puts "Message #{data['message_id']} failed: #{data['error_message']}"
    # Handle failure
    log_message_failure(data['message_id'], data['error_message'])

  else
    puts "Unknown event: #{event}"
  end
end
go
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "io"
    "log"
    "net/http"
    "os"
)

type WebhookPayload struct {
    Event     string                 `json:"event"`
    Timestamp string                 `json:"timestamp"`
    Data      map[string]interface{} `json:"data"`
}

var webhookSecret = os.Getenv("YEBOLINK_WEBHOOK_SECRET")

func handleWebhook(w http.ResponseWriter, r *http.Request) {
    signature := r.Header.Get("X-YeboLink-Signature")
    event := r.Header.Get("X-YeboLink-Event")

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

    // Verify signature
    if !verifySignature(body, signature, webhookSecret) {
        log.Println("Invalid webhook signature")
        http.Error(w, "Invalid signature", http.StatusForbidden)
        return
    }

    // Parse payload
    var payload WebhookPayload
    if err := json.Unmarshal(body, &payload); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }

    // Handle event
    handleEvent(event, payload.Data)

    // Acknowledge receipt
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("OK"))
}

func verifySignature(payload []byte, signature, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(payload)
    expected := hex.EncodeToString(mac.Sum(nil))

    return hmac.Equal([]byte(signature), []byte(expected))
}

func handleEvent(event string, data map[string]interface{}) {
    switch event {
    case "message.sent":
        log.Printf("Message %s sent to %s\n", data["message_id"], data["recipient"])

    case "message.delivered":
        log.Printf("Message %s delivered to %s\n", data["message_id"], data["recipient"])
        // Update your database
        updateMessageStatus(data["message_id"].(string), "delivered")

    case "message.failed":
        log.Printf("Message %s failed: %s\n", data["message_id"], data["error_message"])
        // Handle failure
        logMessageFailure(data["message_id"].(string), data["error_message"].(string))

    default:
        log.Printf("Unknown event: %s\n", event)
    }
}

func main() {
    http.HandleFunc("/webhooks/yebolink", handleWebhook)

    log.Println("Webhook server listening on port 3000")
    log.Fatal(http.ListenAndServe(":3000", nil))
}

Testing Webhooks ​

Local Development with ngrok ​

Use ngrok to expose your local server:

bash
# Start your local server
node server.js

# In another terminal, start ngrok
ngrok http 3000

# Use the ngrok URL for your webhook
# https://abc123.ngrok.io/webhooks/yebolink

Test Webhook Endpoint ​

Send a test request to verify your webhook:

bash
curl -X POST https://your-app.com/webhooks/yebolink \
  -H "Content-Type: application/json" \
  -H "X-YeboLink-Event: message.delivered" \
  -H "X-YeboLink-Signature: test_signature" \
  -d '{
    "event": "message.delivered",
    "timestamp": "2025-11-02T12:00:00Z",
    "data": {
      "message_id": "test-123",
      "channel": "sms",
      "recipient": "+1234567890",
      "status": "delivered"
    }
  }'

Best Practices ​

1. Always Verify Signatures ​

Never trust webhook payloads without verification:

javascript
if (!verifySignature(payload, signature, secret)) {
  return res.status(403).send('Invalid signature');
}

2. Respond Quickly ​

YeboLink expects a 200 response within 10 seconds. Process webhooks asynchronously:

javascript
app.post('/webhooks/yebolink', async (req, res) => {
  // Verify signature
  if (!verifySignature(req.body, signature, secret)) {
    return res.status(403).send('Invalid signature');
  }

  // Acknowledge immediately
  res.status(200).send('OK');

  // Process asynchronously
  const payload = JSON.parse(req.body);
  processWebhookAsync(payload).catch(console.error);
});

3. Handle Retries ​

YeboLink will retry failed webhooks with exponential backoff. Implement idempotency:

javascript
const processedWebhooks = new Set();

function handleWebhookEvent(event, data) {
  const webhookId = `${data.message_id}-${event}`;

  // Check if already processed
  if (processedWebhooks.has(webhookId)) {
    console.log('Webhook already processed, skipping');
    return;
  }

  // Process event
  processEvent(event, data);

  // Mark as processed
  processedWebhooks.add(webhookId);
}

4. Log Webhook Events ​

Keep a log of all webhook events for debugging:

javascript
function logWebhook(event, data) {
  console.log(JSON.stringify({
    timestamp: new Date().toISOString(),
    event,
    message_id: data.message_id,
    status: data.status,
    recipient: data.recipient
  }));
}

5. Monitor Webhook Failures ​

Set up monitoring for webhook failures:

javascript
let failureCount = 0;

app.post('/webhooks/yebolink', async (req, res) => {
  try {
    // Process webhook
    await handleWebhook(req);
    failureCount = 0; // Reset on success
    res.status(200).send('OK');
  } catch (error) {
    failureCount++;

    if (failureCount > 10) {
      // Alert your team
      alertWebhookFailures(failureCount);
    }

    res.status(500).send('Error');
  }
});

Managing Webhooks ​

List Webhooks ​

bash
curl "https://api.yebolink.com/api/v1/webhooks" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Update Webhook ​

bash
curl -X PUT "https://api.yebolink.com/api/v1/webhooks/WEBHOOK_ID" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -d '{
    "events": ["message.delivered", "message.failed"],
    "is_active": true
  }'

Delete Webhook ​

bash
curl -X DELETE "https://api.yebolink.com/api/v1/webhooks/WEBHOOK_ID" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Test a Webhook ​

Before relying on an endpoint, send it a synthetic test event and check the delivery log, or resend an individual failed delivery once you've fixed the receiving end. See Testing & Resending Deliveries.

Next Steps ​

Built with VitePress