Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Azure OpenAI webhooks enable your applications to receive real-time notifications about API events, such as batch completions or incoming calls. By subscribing to webhook events, you can automate workflows, trigger alerts, and integrate with other systems seamlessly. This guide walks you through setting up a webhook server, securing your endpoints, deploying, and troubleshooting common issues.
Prerequisites
Install the required Python packages:
pip install flask openai websockets requests
Webhook server setup
A webhook server is an application that listens for and processes automated messages (webhooks) sent by Azure OpenAI when specific events occur.
Create the webhook listener application
Create a file called app.py with the following Flask application that receives and processes webhook events:
from flask import Flask, request, Response
from openai import OpenAI, InvalidWebhookSignatureError
import os
import logging
app = Flask(__name__)
# Configure logging for Azure App Service
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
client = OpenAI(
# api-key parameter is required, but If you are only using the client for webhooks the key can be a placeholder string
api_key=os.environ.get("OPENAI_API_KEY", "placeholder-key-for-webhooks-only"),
webhook_secret=os.environ["OPENAI_WEBHOOK_SECRET"] # This will be created later
)
@app.route("/webhook", methods=["POST"])
def webhook():
"""Webhook endpoint to receive and process OpenAI events."""
try:
# Unwrap and verify the message using the webhook secret
event = client.webhooks.unwrap(request.data, request.headers)
# Process the event based on type
if event.type == "realtime.call.incoming":
logger.info(f"Received a realtime.call.incoming message for call_id = {event.data.call_id}")
# Add your custom logic here:
# - Log the call details
# - Trigger notifications
# - Update databases
# - Start call processing workflows
elif event.type == "response.completed":
logger.info(f"Received a response.completed message")
# Add your custom logic here
else:
logger.info(f"Received unexpected message of type = {event.type}")
# Always return 200 to acknowledge receipt
return Response(status=200)
except InvalidWebhookSignatureError as e:
logger.error(f"Invalid signature: {e}")
return Response("Invalid signature", status=400)
except Exception as e:
logger.error(f"Error processing webhook: {e}")
return Response("Internal server error", status=500)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=8000, debug=True)
Create a requirements.txt file
Create a requirements.txt file in the same directory as your app.py file:
flask
openai
websockets
requests
Create an Azure web app
Deploy your webhook server using az webapp up. The command must be run from the folder where the app.py code for your application is located.
az webapp up --name unique-webhook-handler-name --resource-group myResourceGroup --runtime "PYTHON:3.12"
This command will:
- Create a resource group if it doesn't exist.
- Create an App Service plan.
- Create a web app.
- Deploy your code.
Your webhook URL will be: https://unique-webhook-handler-name.azurewebsites.net/webhook
Create webhook endpoint
With Azure OpenAI webhook endpoints must be created using the REST API. Register your listener to receive webhook events for specific event types.
import requests
import json
import os
AZURE_OPENAI_API_KEY = os.environ.get("AZURE_OPENAI_API_KEY")
WEBHOOK_NAME = "<WEBHOOK-NAME>" # Give your webhook a custom name
WEBHOOK_URL = "<YOUR-URL>/webhook" # e.g., "https://unique-webhook-handler-name.azurewebsites.net/webhook"
url = "https://<YOUR_RESOURCE_NAME>.openai.azure.com/openai/v1/dashboard/webhook_endpoints"
headers = {
"api-key": AZURE_OPENAI_API_KEY,
"Content-Type": "application/json"
}
data = {
"name": WEBHOOK_NAME,
"url": WEBHOOK_URL,
"event_types": ["response.completed"]
}
response = requests.post(url, headers=headers, json=data)
print(json.dumps(response.json(), indent=2))
Important
The signing secret is only shown once during creation. Secrets can be stored securely using Azure Key Vault.
Configure webhook secret in Azure Web App
Set the webhook signing secret as an environment variable in your Azure Web App:
az webapp config appsettings set --name unique-webhook-handler-name \
--resource-group myResourceGroup \
--settings OPENAI_WEBHOOK_SECRET="<YOUR-SIGNING-SECRET-GOES-HERE>"
After setting the environment variable, restart your web app:
az webapp restart --name unique-webhook-handler-name --resource-group myResourceGroup
Testing webhook messages
First confirm that your web app finished restarting by checking the log stream:
az webapp log tail --name unique-webhook-handler-name --resource-group myResourceGroup
Once the restart is complete, You can test your webhook endpoint by sending sample REST API events.
import requests
import time
import json
WEBHOOK_URL = "https://<unique-webhook-handler-name>.azurewebsites.net/webhook"
TEST_TIMESTAMP = int(time.time())
headers = {
"Content-Type": "application/json",
"Webhook-ID": "test-id",
"Webhook-Timestamp": str(TEST_TIMESTAMP),
"Webhook-Signature": "test-signature"
}
payload = {
"object": "event",
"id": "test-event",
"type": "response.completed",
"created_at": TEST_TIMESTAMP,
"data": {
"call_id": "test-call",
"sip_headers": []
}
}
try:
# timeout in seconds (connect timeout, read timeout)
response = requests.post(WEBHOOK_URL, headers=headers, json=payload, timeout=(5, 30))
print(f"Status Code: {response.status_code}")
print(f"Response: {response.text}")
except requests.exceptions.Timeout:
print("Request timed out!")
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
Note
The initial test signature won't pass validation. For production testing, we can trigger actual events from Azure OpenAI with valid signatures.
Now launch the log stream for your webhook listener webapp if it isn't still running:
az webapp log tail --name unique-webhook-handler-name --resource-group myResourceGroup
To test again with a valid signature make a call with the responses API with background=True set.
from openai import OpenAI
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
token_provider = get_bearer_token_provider(
DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"
)
client = OpenAI(
base_url = "https://<YOUR-RESOURCE-NAME>.openai.azure.com/openai/v1/",
api_key=token_provider,
)
resp = client.responses.create(
model="o4-mini",
input="Write a very long novel about otters in space.",
background=True,
)
print(resp.status)
Python output:
queued
Log stream output:
2025-10-24T23:40:23.623889430Z INFO:app:Received a response.completed message
Security best practices
Securing your webhook endpoint is critical. Follow these recommendations:
Signature verification
Always verify the webhook signature to ensure requests are from Azure OpenAI:
try:
event = client.webhooks.unwrap(request.data, request.headers)
except InvalidWebhookSignatureError:
return Response("Invalid signature", status=400)
Idempotency
Use the Webhook-ID header to prevent duplicate processing:
webhook_id = request.headers.get('Webhook-ID')
if webhook_id in processed_webhooks:
return Response(status=200) # Already processed
Timeout handling
Respond quickly to avoid webhook timeouts. Offload heavy processing to background threads:
def process_call_async(call_data):
# Your heavy processing here
pass
# In webhook handler:
threading.Thread(target=process_call_async, args=(event.data,)).start()
return Response(status=200)
Additional recommendations
- Store your signing secret securely; it's only shown once during creation.
- Use HTTPS for all webhook endpoints.
- Protect and rotate Azure access tokens regularly.
- Validate incoming requests using the signing secret.
Event processing examples
Here are some common patterns for processing webhook events:
Call logging
if event.type == "realtime.call.incoming":
call_data = {
'call_id': event.data.call_id,
'timestamp': event.created_at,
'from': next((h['value'] for h in event.data.sip_headers if h['name'] == 'From'), None),
'to': next((h['value'] for h in event.data.sip_headers if h['name'] == 'To'), None)
}
# Log to database or file
log_call(call_data)
Notification system
if event.type == "realtime.call.incoming":
# Send notification to monitoring system
send_notification({
'type': 'incoming_call',
'call_id': event.data.call_id,
'caller': get_caller_from_headers(event.data.sip_headers)
})
Managing webhook endpoints
List webhook endpoints
curl -X GET https://<YOUR-RESOURCE-NAME>.openai.azure.com/openai/v1/dashboard/webhook_endpoints \
-H "api-key: $AZURE_OPENAI_API_KEY" \
-H "Content-Type: application/json"
Update webhook endpoint
Note
Update webhook properties such as name, URL, and registered event types. The signing secret can't be updated through this operation.
curl -X POST https://<YOUR-RESOURCE-NAME>.openai.azure.com/openai/v1/dashboard/webhook_endpoints/<webhook_id> \
-H "api-key: $AZURE_OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "<UpdatedName>",
"url": "<UpdatedURL>",
"event_types": [
"response.completed",
"response.failed",
"realtime.call.incoming"
]
}'
Delete webhook endpoint
Remove a webhook endpoint using its webhook ID:
curl -X DELETE https://<YOUR-RESOURCE-NAME>.openai.azure.com/openai/v1/dashboard/webhook_endpoints/<webhook_id> \
-H "api-key: $AZURE_OPENAI_API_KEY" \
-H "Accept: application/json"
Common issues and troubleshooting
| Issue | Solution |
|---|---|
| Invalid Signature | Verify your OPENAI_WEBHOOK_SECRET is correct and matches the secret provided at creation. |
| Timeouts | Keep webhook processing under 10 seconds; use background tasks for heavy operations. |
| Missing Events | Ensure your endpoint is publicly accessible and uses HTTPS. Monitor delivery attempts in Azure logs. |
Handling webhook requests on a server
When an event happens that you're subscribed to, you'll receive a POST request with the following structure:
POST https://my-webhook-handler.azurewebsites.net/webhook
User-Agent: OpenAI/1.0 (+https://platform.openai.com/docs/webhooks)
Content-Type: application/json
Webhook-ID: <webhook_eventid>
Webhook-Timestamp: 1750287078
Webhook-Signature: v1,Sample Signature
{
"object": "event",
"id": "evt_685343a1381c819085d44c354e1b330e",
"type": "realtime.call.incoming",
"created_at": 1750287018,
"data": {
"call_id": "some_unique_id",
"sip_headers": [
{
"name": "From",
"value": "sip:+142555512112@sip.example.com"
},
{
"name": "To",
"value": "sip:+18005551212@sip.example.com"
},
{
"name": "Call-ID",
"value": "rtc_9d3d08ea002a4909813d207a592957c4"
}
]
}
}
Header
Webhook-ID: Unique identifier for idempotency - use this to prevent duplicate processingWebhook-Timestamp: Unix timestamp of the delivery attemptWebhook-Signature: Cryptographic signature to verify authenticity from OpenAI
Payload
object: Always "event" for webhook eventsid: Unique event identifiertype: Event type (for example, "realtime.call.incoming")created_at: Unix timestamp when the event was createddata: Event-specific data containing call information and SIP headers
Webhook events reference
The following event types are available for webhook registration:
| Category | Event Type | Description |
|---|---|---|
| Response Events | response.completed |
Response successfully completed |
response.failed |
Response failed | |
response.cancelled |
Response canceled | |
response.incomplete |
Response incomplete | |
| Batch Events | batch.completed |
Batch successfully completed |
batch.failed |
Batch failed | |
batch.cancelled |
Batch canceled | |
batch.expired |
Batch expired | |
| Real-time Events | realtime.call.incoming |
Incoming call event |
Example payload
{
"object": "event",
"id": "evt_685343a1381c819085d44c354e1b330e",
"type": "realtime.call.incoming",
"created_at": 1750287018,
"data": {
"call_id": "some_unique_id",
"sip_headers": [
{ "name": "From", "value": "sip:+142555512112@sip.example.com" },
{ "name": "To", "value": "sip:+18005551212@sip.example.com" },
{ "name": "Call-ID", "value": "rtc_9d3d08ea002a4909813d207a592957c4" }
]
}
}
Clean up resources
When you no longer need the webhook server, you can delete the Azure Web App and its associated resources.
Delete the web app only
To delete just the web app while keeping the resource group and other resources:
az webapp delete --name unique-webhook-handler-name --resource-group myResourceGroup
Additional suggested configurations
- Implement proper logging and monitoring for webhook events.
- Add database storage for call records.
- Set up alerting for failed webhook deliveries.
- Implement retry logic for downstream service calls.
- Add authentication for your webhook endpoint if needed.