Skip to main content
Webhooks allow your application to receive real-time HTTP notifications when events occur in Bota. Instead of polling the API to check for status changes, webhooks push updates to your server as they happen.

Why Use Webhooks?

ApproachProsCons
PollingSimple to implementWastes resources, delayed updates
WebhooksReal-time, efficientRequires endpoint setup
Bota’s async processing model (transcription, summarization) makes webhooks essential. When a transcription completes — which may take seconds to minutes — you’ll know immediately.

Quick Start

1. Create a Webhook Endpoint

Build an HTTP endpoint that accepts POST requests:
const express = require('express');
const app = express();

app.post('/webhooks/bota', express.raw({ type: 'application/json' }), (req, res) => {
  const event = JSON.parse(req.body);

  console.log('Received event:', event.type);

  // Handle the event
  switch (event.type) {
    case 'transcription.completed':
      handleTranscriptionComplete(event.data);
      break;
    case 'recording.uploaded':
      handleRecordingUploaded(event.data);
      break;
  }

  // Return 200 quickly, process async
  res.status(200).send('OK');
});

app.listen(3000);

2. Register the Webhook

curl -X POST https://api.bota.dev/v1/webhooks \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/bota",
    "events": ["transcription.completed", "recording.uploaded"]
  }'
Response:
{
  "id": "wh_abc123",
  "url": "https://your-app.com/webhooks/bota",
  "events": ["transcription.completed", "recording.uploaded"],
  "secret": "whsec_xyz789...",
  "created_at": "2025-01-15T10:00:00Z"
}
Save the secret — you’ll need it to verify webhook signatures. It’s only shown once.

3. Verify Signatures

Always verify that webhooks are from Bota. See Signature Verification below.

Event Types

EventTriggerCommon Use Case
recording.createdRecording entry createdTrack new recordings
recording.uploadedAudio upload completedTrigger processing
recording.deletedRecording deletedClean up cached data
transcription.startedTranscription beginsShow processing status
transcription.completedTranscription finishedDisplay results to user
transcription.failedTranscription failedAlert, retry, or fallback
summary.startedSummary generation beginsShow processing status
summary.completedSummary generatedUpdate UI, send notifications
summary.failedSummary generation failedAlert or retry
See Webhook Events for detailed payload schemas.

Payload Structure

All webhook payloads follow this structure:
{
  "id": "evt_abc123",
  "type": "transcription.completed",
  "created_at": "2025-01-15T10:30:00Z",
  "data": {
    "id": "txn_abc123",
    "recording_id": "rec_xyz789",
    "status": "completed",
    // ... event-specific fields
  }
}
FieldDescription
idUnique event identifier (use for deduplication)
typeEvent type string
created_atWhen the event occurred (ISO 8601)
dataEvent-specific payload

Signature Verification

All webhooks are signed using HMAC-SHA256. Always verify signatures to ensure requests are from Bota.

Signature Header

X-Bota-Signature: sha256=a1b2c3d4e5f6...

Verification Code

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

  const actualSignature = signature.replace('sha256=', '');

  return crypto.timingSafeEqual(
    Buffer.from(expectedSignature),
    Buffer.from(actualSignature)
  );
}

// Express middleware
app.post('/webhooks/bota', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-bota-signature'];

  if (!verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  // Process event...

  res.status(200).send('OK');
});
Never skip signature verification, even in development. Attackers can send fake webhook payloads to trigger unwanted actions.

Retry Policy

Bota retries failed deliveries with exponential backoff:
AttemptDelay After Failure
1Immediate
21 minute
35 minutes
430 minutes
52 hours
68 hours
After 6 failed attempts, the event is marked as failed and no further retries occur.

What Counts as Failure?

  • Non-2xx HTTP response
  • Request timeout (30 seconds)
  • Connection refused
  • TLS/SSL errors

Best Practices

Return a 200 response immediately, then process the event asynchronously. Long-running processing in the request handler will cause timeouts.
app.post('/webhooks/bota', (req, res) => {
  // Return immediately
  res.status(200).send('OK');

  // Process async
  processEventAsync(req.body);
});
The same event may be delivered multiple times due to retries. Use the id field to deduplicate:
const processedEvents = new Set();

function handleEvent(event) {
  if (processedEvents.has(event.id)) {
    return; // Already processed
  }

  processedEvents.add(event.id);
  // Process event...
}
For production, store processed event IDs in a database.
Bota only delivers webhooks to HTTPS URLs. HTTP endpoints are rejected for security.
Events may arrive out of order. Don’t assume recording.uploaded arrives before transcription.completed. Check object state via API if order matters.
Log incoming webhooks for debugging. Include the event ID, type, and any processing errors.
Track your webhook success rate. Frequent failures may indicate endpoint issues.

Testing Webhooks

Local Development

Use a tunneling service like ngrok to receive webhooks locally:
# Start ngrok
ngrok http 3000

# Register the ngrok URL
curl -X POST https://api.sandbox.bota.dev/v1/webhooks \
  -H "Authorization: Bearer sk_test_..." \
  -d '{
    "url": "https://abc123.ngrok.io/webhooks/bota",
    "events": ["transcription.completed"]
  }'

Trigger Test Events

Create a recording and transcription in test mode to trigger events:
# Create a recording (triggers recording.created)
curl -X POST https://api.sandbox.bota.dev/v1/recordings \
  -H "Authorization: Bearer sk_test_..." \
  -d '{"end_user_id": "eu_test_123", "device_id": "dev_test_456"}'

# Start transcription (completes instantly in test mode)
curl -X POST https://api.sandbox.bota.dev/v1/transcriptions \
  -H "Authorization: Bearer sk_test_..." \
  -d '{"recording_id": "rec_test_abc"}'

Managing Webhooks

List Webhooks

curl https://api.bota.dev/v1/webhooks \
  -H "Authorization: Bearer sk_live_..."

Delete a Webhook

curl -X DELETE https://api.bota.dev/v1/webhooks/wh_abc123 \
  -H "Authorization: Bearer sk_live_..."

Update Events (Delete and Recreate)

To change which events a webhook receives, delete it and create a new one.

Troubleshooting

  1. Verify the webhook is registered: GET /webhooks
  2. Check that your endpoint is publicly accessible
  3. Ensure you’re returning 2xx status codes
  4. Check your server logs for incoming requests
  5. Verify the events you’re subscribed to match what you expect
  1. Ensure you’re using the raw request body, not parsed JSON
  2. Check that you’re using the correct webhook secret
  3. Verify you haven’t modified the payload before verification
This is expected behavior. Implement idempotency using the event id field.
This is expected. Don’t rely on event ordering. Check resource state via API if needed.

Next Steps