Crixin
How it worksFeaturesPricingComplianceDevelopers
Back to API Docs

Webhooks

Receive real-time notifications when events happen in your Crixin account.

Overview

Webhooks allow you to receive HTTP POST requests to your server when events occur, such as when a call starts, ends, or when a transcript is ready.

How it works:

  1. Configure your webhook URL in the dashboard
  2. Crixin sends HTTP POST requests to your URL when events occur
  3. Your server processes the event and returns a 200 response
  4. If your server fails to respond, we retry with exponential backoff
Setup

1. Create a webhook endpoint

Create an HTTP endpoint on your server that accepts POST requests.

// Express.js example
app.post('/webhooks/crixin', (req, res) => {
  const event = req.body;
  console.log('Received event:', event.type);

  // Process the event
  // ...

  res.status(200).json({ received: true });
});

2. Configure in Dashboard

Go to Dashboard → Developers and add your webhook URL.

3. Verify signatures

Always verify webhook signatures to ensure requests are from Crixin.

Event Types
call.started

Sent when an inbound call connects to your assistant.

{
  "type": "call.started",
  "data": {
    "callId": "call_abc123",
    "assistantId": "asst_xyz789",
    "assistantName": "Support Agent",
    "phoneNumber": "+14155551234",
    "callerNumber": "+14155559999",
    "direction": "inbound",
    "startedAt": "2025-01-15T10:30:00Z"
  },
  "timestamp": "2025-01-15T10:30:00Z"
}
call.completed

Sent when a call ends successfully.

{
  "type": "call.completed",
  "data": {
    "callId": "call_abc123",
    "assistantId": "asst_xyz789",
    "duration": 127,
    "endedAt": "2025-01-15T10:32:07Z",
    "summary": "Customer inquired about order status for order #12345"
  },
  "timestamp": "2025-01-15T10:32:07Z"
}
call.failed

Sent when a call fails due to an error.

{
  "type": "call.failed",
  "data": {
    "callId": "call_abc123",
    "assistantId": "asst_xyz789",
    "error": {
      "code": "ASSISTANT_UNAVAILABLE",
      "message": "The assistant is not currently active"
    },
    "failedAt": "2025-01-15T10:30:05Z"
  },
  "timestamp": "2025-01-15T10:30:05Z"
}
call.missed

Sent when a call was not answered.

{
  "type": "call.missed",
  "data": {
    "callId": "call_abc123",
    "assistantId": "asst_xyz789",
    "callerNumber": "+14155559999",
    "reason": "no_answer"
  },
  "timestamp": "2025-01-15T10:30:30Z"
}
transcript.ready

Sent when the call transcript has been processed and is available.

{
  "type": "transcript.ready",
  "data": {
    "callId": "call_abc123",
    "assistantId": "asst_xyz789",
    "messageCount": 12,
    "duration": 127
  },
  "timestamp": "2025-01-15T10:32:30Z"
}
usage.threshold

Sent when usage reaches 80%, 90%, or 100% of included minutes.

{
  "type": "usage.threshold",
  "data": {
    "threshold": 80,
    "minutesUsed": 400,
    "minutesIncluded": 500,
    "tier": "starter"
  },
  "timestamp": "2025-01-15T10:32:30Z"
}
Security: Verifying Signatures

Every webhook request includes a signature header that you should verify to ensure the request came from Crixin and wasn't tampered with.

Headers included:

  • X-Crixin-Signature - HMAC-SHA256 signature
  • X-Crixin-Timestamp - Unix timestamp of the request

Node.js Example

const crypto = require('crypto');

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

  // Compute expected signature
  const payload = `${timestamp}.${JSON.stringify(body)}`;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  // Compare signatures (timing-safe)
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Usage in Express
app.post('/webhooks/crixin', (req, res) => {
  const signature = req.headers['x-crixin-signature'];
  const timestamp = req.headers['x-crixin-timestamp'];

  if (!verifyWebhook(req.body, signature, timestamp, process.env.WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process event...
  res.json({ received: true });
});

Python Example

import hmac
import hashlib
import time
import json

def verify_webhook(body: dict, signature: str, timestamp: str, secret: str) -> bool:
    # Check timestamp is within 5 minutes
    now = int(time.time())
    if abs(now - int(timestamp)) > 300:
        return False

    # Compute expected signature
    payload = f"{timestamp}.{json.dumps(body)}"
    expected = hmac.new(
        secret.encode('utf-8'),
        payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    # Compare signatures (timing-safe)
    return hmac.compare_digest(signature, expected)

# Usage in Flask
@app.route('/webhooks/crixin', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Crixin-Signature')
    timestamp = request.headers.get('X-Crixin-Timestamp')

    if not verify_webhook(request.json, signature, timestamp, os.environ['WEBHOOK_SECRET']):
        return jsonify({'error': 'Invalid signature'}), 401

    # Process event...
    return jsonify({'received': True})
Retry Policy

If your endpoint returns a non-2xx status code or times out, we'll retry the request with exponential backoff.

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry (final)24 hours

Important

Your endpoint should respond within 30 seconds. If processing takes longer, acknowledge the webhook immediately and process asynchronously.

Best Practices
  • ✓
    Respond quickly

    Return a 200 response immediately, then process the event asynchronously.

  • ✓
    Handle duplicates

    Use the event ID to deduplicate. Retries may cause the same event to be sent multiple times.

  • ✓
    Verify signatures

    Always verify the webhook signature before processing any data.

  • ✓
    Use HTTPS

    Only use HTTPS endpoints. We won't send webhooks to HTTP URLs.

  • ✓
    Log everything

    Log webhook payloads for debugging. Include the request ID in your logs.

Testing Webhooks

Test your webhook endpoint locally before deploying to production.

Using ngrok for local testing

# Install ngrok
brew install ngrok  # macOS
# or download from ngrok.com

# Start your local server
npm run dev  # or your server start command

# In another terminal, expose your local server
ngrok http 3000

# Use the ngrok URL in your Crixin dashboard
# https://abc123.ngrok.io/webhooks/crixin

Test with curl

# Send a test webhook to your endpoint
curl -X POST http://localhost:3000/webhooks/crixin \
  -H "Content-Type: application/json" \
  -H "X-Crixin-Signature: test_signature" \
  -H "X-Crixin-Timestamp: $(date +%s)" \
  -d '{
    "type": "call.completed",
    "data": {
      "callId": "call_test123",
      "duration": 60
    },
    "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
  }'