Webhooks
Receive real-time notifications for bot events.
Overview
Configure a callbackUrl when creating a bot to receive event notifications:
{
"meetingInfo": {
"platform": "google",
"meetingUrl": "https://meet.google.com/abc-defg-hij"
},
"callbackUrl": "https://your-server.com/webhooks/meetbot"
}
Webhook Payload
All webhooks are sent as POST requests with JSON body:
{
"event": "bot.joined",
"botId": 1,
"timestamp": "2024-01-15T10:30:00Z",
"data": {}
}
Events
Bot Lifecycle
| Event | Description |
|---|---|
bot.deploying | Bot container is starting |
bot.joining | Bot is joining the meeting |
bot.joined | Bot successfully joined |
bot.in_waiting_room | Bot is in waiting room |
bot.left | Bot left the meeting |
bot.error | An error occurred |
Recording
| Event | Description |
|---|---|
recording.started | Recording began |
recording.stopped | Recording ended |
recording.ready | Recording is available for download |
Participants
| Event | Description |
|---|---|
participant.joined | Someone joined the meeting |
participant.left | Someone left the meeting |
Event Payloads
bot.joined
{
"event": "bot.joined",
"botId": 1,
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"meetingTitle": "Team Standup",
"platform": "google"
}
}
bot.error
{
"event": "bot.error",
"botId": 1,
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"error": "Failed to join meeting",
"code": "JOIN_FAILED"
}
}
recording.ready
{
"event": "recording.ready",
"botId": 1,
"timestamp": "2024-01-15T11:30:00Z",
"data": {
"durationSeconds": 3600,
"fileSizeBytes": 157286400
}
}
participant.joined
{
"event": "participant.joined",
"botId": 1,
"timestamp": "2024-01-15T10:35:00Z",
"data": {
"participantName": "John Doe"
}
}
Webhook Security
Headers
Every webhook delivery includes the following headers:
| Header | Example | Purpose |
|---|---|---|
X-Webhook-Signature | sha256={hmac} | HMAC-SHA256 of {timestamp}.{body} |
X-Webhook-Timestamp | 1705312200 | Delivery time (Unix seconds), included in signature |
X-Webhook-Id | whdel_a1b2c3... | Unique delivery ID for idempotency |
X-Webhook-Event | bot.joined | Event type string |
Signature Verification
The signature is computed over {timestamp}.{body} using your webhook secret. Always verify the signature and reject deliveries older than 5 minutes to prevent replay attacks.
import hmac
import hashlib
import time
def verify_webhook(payload: bytes, signature: str, timestamp: str, secret: str) -> bool:
# Reject deliveries older than 5 minutes
if abs(time.time() - int(timestamp)) > 300:
return False
signed_content = f"{timestamp}.{payload.decode()}"
expected = hmac.new(
secret.encode(),
signed_content.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
const crypto = require('crypto');
function verifyWebhook(payload, signature, timestamp, secret) {
// Reject deliveries older than 5 minutes
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return false;
}
const signedContent = `${timestamp}.${payload}`;
const expected = crypto
.createHmac('sha256', secret)
.update(signedContent)
.digest('hex');
return signature === `sha256=${expected}`;
}
Webhook Management API
You can create and manage webhooks programmatically.
Create Webhook
POST /webhooks
curl -X POST https://api.meetbot.dev/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Webhook",
"url": "https://your-server.com/webhooks/meetbot",
"events": ["bot.joined", "bot.ended", "recording.available"],
"isActive": true
}'
Response includes a secret field (only shown once):
{
"id": 1,
"name": "Production Webhook",
"url": "https://your-server.com/webhooks/meetbot",
"events": ["bot.joined", "bot.ended", "recording.available"],
"secret": "whsec_abc123...",
"isActive": true,
"createdAt": "2024-01-15T10:30:00Z"
}
The full secret is only shown once on creation. Store it securely!
List Webhooks
GET /webhooks
Get Webhook
GET /webhooks/{id}
Includes delivery statistics.
Update Webhook
PATCH /webhooks/{id}
Delete Webhook
DELETE /webhooks/{id}
Test Webhook
POST /webhooks/{id}/test
Sends a test payload to the webhook URL and returns the result.
Delivery History
GET /webhooks/{id}/deliveries
Returns paginated delivery logs with status codes, response times, and payloads.
Webhook Secret
Use the secret returned when creating a webhook to verify delivery signatures.
Retry Policy
Failed webhooks are retried with exponential backoff:
| Attempt | Delay | Cumulative Time |
|---|---|---|
| 1st retry | 30 seconds | 30s |
| 2nd retry | 1 minute | 1m 30s |
| 3rd retry | 2 minutes | 3m 30s |
| 4th retry | 4 minutes | 7m 30s |
| 5th retry | 8 minutes | 15m 30s |
Webhooks are considered failed if:
- Response status is not 2xx
- Connection timeout (10 seconds)
- No response within 30 seconds
Best Practices
- Respond quickly: Return 200 immediately, process async
- Handle duplicates: Events may be sent multiple times
- Verify signatures: Always validate webhook authenticity
- Use HTTPS: Only use secure endpoints in production
- Log events: Keep records for debugging
Testing Webhooks
Use tools like ngrok for local development:
# Start ngrok
ngrok http 3000
# Use the ngrok URL as callback
{
"callbackUrl": "https://abc123.ngrok.io/webhooks/meetbot"
}