WABridges
OVERVIEW DELIVERY EVENTS HANDLING
Full API Docs

WEBHOOKS

HOW WEBHOOKS WORK

Every inbound WhatsApp event fires a POST request to the webhook_url you configure on each bridge. Your server receives the event in real time and responds with 200.

Set the webhook URL when provisioning a bridge:

PROVISION WITH WEBHOOK
curl -X POST https://wabridges.com/api/instances \
  -H "Authorization: Bearer $WA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"customer_ref":"user-123","webhook_url":"https://yourbackend.com/hook"}'

You can update the webhook URL at any time from the bridge detail page in your dashboard.

All events share a common shape: a JSON object with an event string field identifying the type. Switch on that field to route events to the right handler.

COMMON SHAPE
POST https://yourbackend.com/hook
Content-Type: application/json

{"event":"message", ...event-specific fields}

DELIVERY BEHAVIOR

MethodPOST
Content-Typeapplication/json
Expected response200 OK (body ignored)
Timeout10 seconds
RetriesNone - each event is delivered once
OrderingBest-effort, not guaranteed

Respond fast. Return 200 immediately and do any processing asynchronously. If your handler takes longer than 10 seconds the delivery is considered failed and the event is dropped.

Design your handler to be idempotent. While retries are not automatic, network failures at your end can cause duplicate deliveries in rare cases.

EVENT TYPES

WEBHOOK message

Fields: event, message_id, contact_id, phone, chat_id, name, body, type, media_type, is_group, from_me, timestamp

EXAMPLE PAYLOAD
{"event":"message","message_id":"ACE41E...","contact_id":"15550001234@s.whatsapp.net","phone":"15550001234","chat_id":"15550001234@s.whatsapp.net","name":"Alice","body":"Hello!","type":"text","media_type":"","is_group":false,"from_me":false,"timestamp":1777180605}
WEBHOOK connected

Fields: event, phone

EXAMPLE PAYLOAD
{"event":"connected","phone":"15550001234"}
WEBHOOK disconnected

Fields: event

EXAMPLE PAYLOAD
{"event":"disconnected"}
WEBHOOK typing

Fields: event, contact_id, chat_id, state (composing|paused), mode (text|voice)

EXAMPLE PAYLOAD
{"event":"typing","contact_id":"15550001234@s.whatsapp.net","chat_id":"15550001234@s.whatsapp.net","state":"composing","mode":"text"}
WEBHOOK poll_vote

Fields: event, contact_id, phone, chat_id, poll_id, message_id, name, from_me, timestamp, selected_options

EXAMPLE PAYLOAD
{"event":"poll_vote","contact_id":"15550001234@s.whatsapp.net","phone":"15550001234","chat_id":"15550001234@s.whatsapp.net","poll_id":"ACPOLL0001","message_id":"ACVOTE0001","name":"Alice","from_me":false,"timestamp":1777330500,"selected_options":["Red"]}
WEBHOOK event_response

Fields: event, contact_id, phone, chat_id, event_id, message_id, name, from_me, timestamp, response (going|not_going|maybe), extra_guest_count

EXAMPLE PAYLOAD
{"event":"event_response","contact_id":"15550001234@s.whatsapp.net","phone":"15550001234","chat_id":"15550009999@g.us","event_id":"ACEVENT0001","message_id":"ACEVRESP0001","name":"Alice","from_me":false,"timestamp":1777330700,"response":"going","extra_guest_count":0}
WEBHOOK call_incoming

Fields: event, call_id, contact_id, platform, timestamp

EXAMPLE PAYLOAD
{"event":"call_incoming","call_id":"ABCDEF123456","contact_id":"15550001234@s.whatsapp.net","platform":"android","timestamp":1777180605}
WEBHOOK call_terminated

Fields: event, call_id, contact_id, reason (timeout|hangup|decline|busy), timestamp

EXAMPLE PAYLOAD
{"event":"call_terminated","call_id":"ABCDEF123456","contact_id":"15550001234@s.whatsapp.net","reason":"hangup","timestamp":1777180720}
WEBHOOK offline_sync_preview

Fields: event, total, messages, notifications, receipts, app_data_changes

EXAMPLE PAYLOAD
{"event":"offline_sync_preview","total":142,"messages":120,"notifications":8,"receipts":14,"app_data_changes":0}
WEBHOOK offline_sync_completed

Fields: event, count

EXAMPLE PAYLOAD
{"event":"offline_sync_completed","count":142}
WEBHOOK profile_picture_updated

Fields: event, remove (bool), picture_id?, timestamp

EXAMPLE PAYLOAD
{"event":"profile_picture_updated","remove":false,"picture_id":"12345678901","timestamp":1777180605}

HANDLING WEBHOOKS

The pattern is the same in every language: parse the body, switch on event, return 200 before doing any heavy work.

Node.js (Express)

JAVASCRIPT
const express = require('express')
const app = express()
app.use(express.json())

app.post('/hook', (req, res) => {
  res.sendStatus(200) // respond immediately

  const { event, ...data } = req.body

  switch (event) {
    case 'message':
      if (!data.from_me) {
        console.log(`${data.name}: ${data.body}`)
        // reply, store, trigger workflow...
      }
      break
    case 'connected':
      console.log(`bridge connected, phone=${data.phone}`)
      break
    case 'disconnected':
      console.warn('bridge disconnected')
      break
    case 'typing':
      // data.state = 'composing' | 'paused'
      break
  }
})

app.listen(3000)

Python (Flask)

PYTHON
from flask import Flask, request, jsonify
import threading

app = Flask(__name__)

def process_event(payload):
    event = payload.get('event')
    if event == 'message' and not payload.get('from_me'):
        print(f"{payload['name']}: {payload['body']}")
        # reply, store, trigger workflow...
    elif event == 'connected':
        print(f"bridge connected, phone={payload['phone']}")
    elif event == 'disconnected':
        print('bridge disconnected')

@app.route('/hook', methods=['POST'])
def webhook():
    payload = request.get_json()
    # process async so we return 200 immediately
    threading.Thread(target=process_event, args=(payload,)).start()
    return '', 200

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

PHP

PHP
<?php
$payload = json_decode(file_get_contents('php://input'), true);
http_response_code(200); // respond immediately

$event = $payload['event'] ?? '';

switch ($event) {
    case 'message':
        if (empty($payload['from_me'])) {
            error_log("{$payload['name']}: {$payload['body']}");
            // reply, store, trigger workflow...
        }
        break;
    case 'connected':
        error_log("bridge connected, phone={$payload['phone']}");
        break;
    case 'disconnected':
        error_log('bridge disconnected');
        break;
}