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"

Next Steps

Built with VitePress