- Overview
- Architecture
- Supported Webhook Events
- Implementation Details
- Webhook Management
- Testing
- Troubleshooting
- Production Deployment
This document provides comprehensive information about the OpenPhone webhook integration. Based on actual API documentation and testing, OpenPhone provides 6 webhook event types that enable real-time updates for messages, calls, and AI-generated content.
message.received- Incoming message receivedmessage.delivered- Outgoing message deliveredcall.completed- Call finished (any status)call.recording.completed- Call recording is readycall.summary.completed- AI call summary generatedcall.transcript.completed- AI call transcript generated
-
services/openphone_webhook_service.py(360 lines) ✅- Streamlined implementation matching current database schema
- Supports media attachments
- Clean, maintainable architecture
-
routes/api_routes.py- Contains
/api/webhooks/openphoneendpoint - Uses
OpenPhoneWebhookService - HMAC signature verification
- Contains
-
manage_webhooks.py- Webhook management utility
- Can list, create, test, and delete webhooks
- Simpler Code - Focused on actual available events
- Media Support - Captures image/file attachments
- Better Testing - Comprehensive test coverage
- Current Schema - Matches actual database models
- Cleaner Separation - Each webhook type has dedicated handler
{
"type": "message.received",
"data": {
"object": {
"id": "MSG123456789",
"conversationId": "CONV123456",
"direction": "incoming",
"from": "+1234567890",
"to": ["+0987654321"],
"text": "Message content",
"media": [
"https://media.openphone.com/attachment1.jpg",
"https://media.openphone.com/attachment2.pdf"
],
"status": "received",
"createdAt": "2025-07-30T10:00:00.000Z"
}
}
}Key Features:
- ✅
mediaarray contains URLs for attachments (images, PDFs, etc.) - ✅ Automatically creates/updates contact and conversation
- ✅ Stores media URLs in database for display
Updates the delivery status of sent messages.
{
"type": "call.completed",
"data": {
"object": {
"id": "CALL123456789",
"status": "completed",
"duration": 300,
"participants": ["+1234567890", "+0987654321"],
"recordingUrl": "https://api.openphone.com/v1/call-recordings/CALL123456789",
"answeredAt": "2025-07-30T10:00:05.000Z",
"completedAt": "2025-07-30T10:05:00.000Z"
}
}
}Key Features:
- ✅ Stores call duration and recording URL
- ✅ Links to existing conversation or creates new one
- ✅ Recording URL may be included if available immediately
{
"type": "call.recording.completed",
"data": {
"object": {
"id": "REC123456789",
"callId": "CALL123456789",
"url": "https://api.openphone.com/v1/call-recordings/CALL123456789",
"duration": 300,
"size": 2400000
}
}
}Key Features:
- ✅ Fired when call recording is ready (separate from call.completed)
- ✅ Updates existing call activity with recording URL
- ✅ Includes recording duration and file size
{
"type": "call.summary.completed",
"data": {
"object": {
"callId": "CALL123456789",
"summary": "Customer inquired about pricing...",
"keyPoints": ["Point 1", "Point 2"],
"nextSteps": ["Action 1", "Action 2"],
"sentiment": "positive"
}
}
}Key Features:
- ✅ AI-generated summary attached to existing call
- ✅ Includes key points and recommended next steps
- ✅ Sentiment analysis
{
"type": "call.transcript.completed",
"data": {
"object": {
"callId": "CALL123456789",
"transcript": {
"dialogue": [
{
"speaker": "Agent",
"text": "Hello, how can I help?",
"timestamp": "00:00:02"
}
],
"confidence": 0.95
}
}
}
}Key Features:
- ✅ Full call transcript with speaker identification
- ✅ Timestamps for each dialogue segment
- ✅ Confidence score
All webhooks are verified using HMAC-SHA256 signature:
signature = hmac.new(
key=OPENPHONE_WEBHOOK_SIGNING_KEY.encode('utf-8'),
msg=request_body,
digestmod=hashlib.sha256
).hexdigest()The signature is passed in the x-openphone-signature-v1 header.
Webhooks update the following models:
openphone_id: Unique ID from OpenPhoneactivity_type: 'message' or 'call'direction: 'incoming' or 'outgoing'body: Message textmedia_urls: JSON array of media attachment URLsrecording_url: URL for call recordingsai_summary: AI-generated call summaryai_transcript: AI-generated call transcriptstatus: Current status
event_id: Webhook event IDevent_type: Type of webhookpayload: Complete webhook payload (JSON)processed: Boolean flagerror_message: Any processing errors
-
Media Attachment Support 📎
- Message webhooks capture the
mediaarray - Media URLs stored in
activity.media_urls - UI displays media attachments
- Message webhooks capture the
-
Call Recording Handling 🎙️
call.completedincludes recording URL if availablecall.recording.completedupdates call when ready later- Handles async recording processing
-
AI Content Integration 🤖
- Call summaries with key points and next steps
- Full transcripts with speaker identification
- Sentiment analysis
-
Idempotency
- Webhooks are idempotent - same event can be processed multiple times safely
-
Automatic Contact Creation
- Unknown phone numbers automatically create new contacts
-
Conversation Grouping
- Messages/calls are grouped by contact into conversations
python manage_webhooks.py listpython manage_webhooks.py createThis creates a single webhook subscription with all 6 events:
{
"url": "https://your-domain.com/api/webhooks/openphone",
"events": [
"message.received",
"message.delivered",
"call.completed",
"call.recording.completed",
"call.summary.completed",
"call.transcript.completed"
]
}python manage_webhooks.py testpython manage_webhooks.py deletepython test_webhook_handler.py allpython test_webhook_handler.py mediapython test_webhook_handler.py aipython test_webhook_handler.py full# Generate signature (requires signing key)
PAYLOAD='{"type":"message.received","data":{"object":{"id":"TEST123"}}}'
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SIGNING_KEY" | cut -d' ' -f2)
# Send webhook
curl -X POST http://localhost:5000/api/webhooks/openphone \
-H "Content-Type: application/json" \
-H "x-openphone-signature-v1: $SIGNATURE" \
-d "$PAYLOAD"Test payloads are available in webhook_payload_examples.py for all event types.
-
403 Forbidden
- Check webhook signing key is configured correctly
- Verify
OPENPHONE_WEBHOOK_SIGNING_KEYenvironment variable
-
404 Not Found
- Ensure webhook endpoint URL is correct
- Check
WEBHOOK_BASE_URLenvironment variable
-
500 Server Error
- Check logs for database or processing errors
- Verify database schema matches expectations
Enable detailed logging:
import logging
logging.getLogger('services.openphone_webhook_service').setLevel(logging.DEBUG)- Watch logs for webhook events
- Verify media attachments appear in UI
- Check AI summaries/transcripts populate correctly
- Monitor
webhook_eventstable for processing status
# Set required environment variables
export OPENPHONE_WEBHOOK_SIGNING_KEY="your-signing-key"
export WEBHOOK_BASE_URL="https://your-domain.com"
export OPENPHONE_API_KEY="your-api-key"# Remove deprecated webhook service
rm services/webhook_sync_service.py
# Test everything still works
python test_webhook_handler.py full# Create webhook subscriptions
python manage_webhooks.py create
# Verify they're active
python manage_webhooks.py list- Watch application logs for incoming webhook events
- Verify media attachments appear in conversations
- Check that AI summaries and transcripts populate
- Monitor webhook processing success rate
- Limited Event Types: Only 6 webhook types are available (not the 15+ we initially thought)
- No Intermediate States: No
.started,.answered, or.missedevents - Completion Events Only: All events use
.completedsuffix - No Outgoing Message Events: We don't get notified when messages are sent (only delivered)
- Media Discovery: The
mediaarray in messages was not documented but is available!
- All webhooks are idempotent (safe to replay)
- Old events won't break the handler
- Extensive logging for debugging
- Test suite validates all scenarios
- Automatic error recovery and retry logic