Outbound Webhooks#

Subscribe to Outeract events and receive them at your endpoint in real-time.

Overview#

Outbound webhooks allow you to:

  • Receive messages as they arrive
  • Track message delivery status
  • React to custom events
  • Build real-time integrations

Creating a Subscription#

GraphQL#

mutation {
  createWebhookSubscription(
    name: "Message Handler"
    targetUrl: "https://myapp.com/webhooks/outeract"
    eventPatterns: ["message.inbound", "message.outbound"]
    secret: "my-webhook-secret-32chars-minimum"
  ) {
    id
    name
    targetUrl
    eventPatterns
    status
  }
}

REST#

curl -X POST https://api.outeract.com/v1/webhook-subscriptions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "X-Outeract-App-ID: app_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Message Handler",
    "target_url": "https://myapp.com/webhooks/outeract",
    "event_patterns": ["message.inbound", "message.outbound"],
    "secret": "my-webhook-secret-32chars-minimum"
  }'

Event Patterns#

Event patterns determine which events trigger your webhook.

Exact Match#

["message.inbound", "message.outbound"]

Only matches these exact event types.

Wildcard Patterns#

["message.*"]

Matches any event starting with message.:

  • message.inbound
  • message.outbound
  • message.delivered
  • message.read

All Events#

["*"]

Matches all events (use with caution).

Common Patterns#

PatternMatches
message.*All message events
message.inboundIncoming messages only
message.outboundOutgoing messages only
message.statusDelivery status updates
custom.*All custom events
user.*User-related events
link_code.*Link code events

Webhook Payload#

Structure#

{
  "id": "whd_abc123",
  "subscription_id": "ws_xyz789",
  "event_id": "evt_123456",
  "event_type": "message.inbound",
  "app_id": "app_abc123",
  "payload": {
    "message": {
      "text": "Hello!",
      "role": "user"
    },
    "platform": "whatsapp",
    "external_message_id": "wamid.xyz"
  },
  "edges": {
    "sent_by": {
      "platform_user_id": "pu_xyz789",
      "external_id": "+14155551234",
      "name": "John Doe"
    },
    "sent_to": {
      "platform_user_id": "pu_system",
      "external_id": "+14155550000",
      "is_system_user": true
    }
  },
  "created_at": "2024-01-15T10:30:00Z",
  "timestamp": "2024-01-15T10:30:01Z"
}

Fields#

FieldTypeDescription
idstringUnique delivery ID
subscription_idstringYour subscription ID
event_idstringSource event ID
event_typestringEvent type (e.g., message.inbound)
app_idstringYour application ID
payloadobjectEvent-specific data
edgesobjectRelated entities (sender, recipient, files)
created_atdatetimeWhen event was created
timestampdatetimeDelivery timestamp

Verifying Webhooks#

Every webhook includes a signature for verification.

Headers#

X-Outeract-Signature: sha256=abc123...
X-Outeract-Timestamp: 1705320600
X-Outeract-Delivery-ID: whd_abc123

Verification (Python)#

import hmac
import hashlib
import time

def verify_webhook(
    body: bytes,
    signature: str,
    timestamp: str,
    secret: str,
    tolerance: int = 300  # 5 minutes
) -> bool:
    # Check timestamp to prevent replay attacks
    current_time = int(time.time())
    webhook_time = int(timestamp)
    if abs(current_time - webhook_time) > tolerance:
        return False

    # Construct signed payload
    signed_payload = f"{timestamp}.{body.decode()}"

    # Calculate expected signature
    expected = "sha256=" + hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()

    # Constant-time comparison
    return hmac.compare_digest(expected, signature)

Verification (Node.js)#

const crypto = require('crypto');

function verifyWebhook(body, signature, timestamp, secret, tolerance = 300) {
    // Check timestamp
    const currentTime = Math.floor(Date.now() / 1000);
    const webhookTime = parseInt(timestamp);
    if (Math.abs(currentTime - webhookTime) > tolerance) {
        return false;
    }

    // Calculate expected signature
    const signedPayload = `${timestamp}.${body}`;
    const expected = 'sha256=' + crypto
        .createHmac('sha256', secret)
        .update(signedPayload)
        .digest('hex');

    // Constant-time comparison
    return crypto.timingSafeEqual(
        Buffer.from(expected),
        Buffer.from(signature)
    );
}

Responding to Webhooks#

Success Response#

Return HTTP 200-299 within 30 seconds:

{
  "received": true
}

Or simply:

HTTP/1.1 200 OK

Error Response#

If processing fails, return 4xx or 5xx:

HTTP/1.1 500 Internal Server Error

{
  "error": "Processing failed"
}

The webhook will be retried according to the retry policy.

Retry Policy#

Failed deliveries are retried with exponential backoff:

AttemptDelayTotal Time
1Immediate0
21 minute1 minute
35 minutes6 minutes
430 minutes36 minutes
52 hours~2.5 hours

After 5 failures, the delivery is marked as failed.

Retry Headers#

On retries, additional headers are included:

X-Outeract-Retry-Count: 2
X-Outeract-Original-Timestamp: 1705320600

Managing Subscriptions#

List Subscriptions#

query {
  webhookSubscriptions {
    id
    name
    targetUrl
    eventPatterns
    status
    createdAt
  }
}

Update Subscription#

mutation {
  updateWebhookSubscription(
    id: "ws_abc123"
    name: "Updated Name"
    eventPatterns: ["message.*", "custom.*"]
  ) {
    id
    name
    eventPatterns
  }
}

Pause Subscription#

mutation {
  updateWebhookSubscription(
    id: "ws_abc123"
    status: PAUSED
  ) {
    id
    status
  }
}

Delete Subscription#

mutation {
  deleteWebhookSubscription(id: "ws_abc123")
}

Delivery Logs#

View webhook delivery history:

query {
  webhookDeliveryLogs(
    webhookSubscriptionId: "ws_abc123"
    first: 20
  ) {
    edges {
      node {
        id
        eventId
        eventType
        status
        statusCode
        attemptCount
        duration
        errorMessage
        createdAt
      }
    }
  }
}

Status Values#

StatusDescription
pendingQueued for delivery
deliveredSuccessfully delivered
failedAll retries exhausted
retryingFailed, will retry

Testing Webhooks#

Test Endpoint#

Send a test event to your webhook:

mutation {
  testWebhookSubscription(id: "ws_abc123") {
    success
    statusCode
    responseBody
    duration
  }
}

Webhook Debugging Tools#

  1. webhook.site - Inspect payloads online
  2. ngrok - Expose local server
  3. RequestBin - Collect and inspect requests

Local Development#

# Start ngrok
ngrok http 3000

# Use the ngrok URL as your webhook endpoint
# https://abc123.ngrok.io/webhooks/outeract

Best Practices#

1. Return Fast#

Process webhooks asynchronously:

from fastapi import FastAPI, BackgroundTasks

app = FastAPI()

@app.post("/webhooks/outeract")
async def webhook(request: Request, background_tasks: BackgroundTasks):
    body = await request.body()

    # Verify signature
    if not verify_webhook(body, request.headers):
        return JSONResponse(status_code=401)

    # Queue for async processing
    background_tasks.add_task(process_event, json.loads(body))

    return {"received": True}

2. Handle Duplicates#

Use event_id for idempotency:

processed_events = set()  # Use Redis/DB in production

async def process_event(data):
    event_id = data["event_id"]

    if event_id in processed_events:
        return  # Already processed

    processed_events.add(event_id)
    # Process event...

3. Secure Your Endpoint#

  • Always verify signatures
  • Use HTTPS
  • Validate timestamp to prevent replay attacks
  • Keep your secret secure

4. Monitor and Alert#

  • Track webhook delivery success rate
  • Alert on high failure rates
  • Monitor response times

5. Use Meaningful Patterns#

Subscribe only to events you need:

// Good - specific patterns
["message.inbound", "message.status"]

// Avoid - too broad
["*"]

Event Types Reference#

Message Events#

Event TypeDescription
message.inboundIncoming message received
message.outboundOutgoing message sent
message.deliveredMessage delivered
message.readMessage read
message.failedMessage delivery failed
message.reactionMessage reaction
Event TypeDescription
link_code.generatedLink code created
link_code.activationLink code used

User Events#

Event TypeDescription
user.mergedUsers merged

Custom Events#

Your custom event types:

custom.order.created
custom.payment.completed
custom.support.ticket.opened