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/webhooksAuthentication
Requires JWT token via Authorization: Bearer header.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Your webhook endpoint URL (must be HTTPS) |
events | array | Yes | Array of event types to subscribe to |
Available Events
message.sent- Message sent to providermessage.delivered- Message delivered to recipientmessage.failed- Message delivery failedmessage.queued- Message queued for sending
Example Requests
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"]
}'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']
);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
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
{
"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
{
"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 verificationX-YeboLink-Event: Event type (e.g.,message.delivered)Content-Type:application/json
Implementing Webhook Handler
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');
});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
// 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");
}
}
?>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
endpackage 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:
# 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/yebolinkTest Webhook Endpoint
Send a test request to verify your webhook:
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:
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:
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:
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:
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:
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
curl "https://api.yebolink.com/api/v1/webhooks" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Update Webhook
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
curl -X DELETE "https://api.yebolink.com/api/v1/webhooks/WEBHOOK_ID" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Next Steps
- Webhook Events - Complete event reference
- Signature Verification - Detailed verification guide
- API Reference - Complete API documentation