Skip to content

Fix Odoo Webhook Delivery Failed: Timeouts, Authentication, and Payload Errors

DeployMonkey Team · March 23, 2026 10 min read

The Webhook Delivery Problem

Webhooks in Odoo notify external systems when events occur — a sale order is confirmed, an invoice is paid, or a customer is created. When webhook delivery fails, integrations break silently. The external system never receives the notification, and data goes out of sync.

Common Webhook Errors

# In Odoo logs:
WARNING odoo.addons.base_automation: Webhook delivery failed
HTTPError: 401 Unauthorized

# Or:
ConnectionError: HTTPSConnectionPool(host='api.example.com', port=443):
    Max retries exceeded with url: /webhook/odoo

# Or:
TimeoutError: Request to https://api.example.com/webhook timed out after 10s

# Or:
HTTPError: 500 Internal Server Error

How Odoo Webhooks Work

Odoo can send webhooks through:

  • Automated Actions (base_automation) — trigger on record create/write/delete/time
  • Custom code — using requests library in server actions or cron jobs
  • Third-party modules — webhook modules from OCA or Odoo Apps

Cause 1: Endpoint URL Wrong or Unreachable

# The webhook URL is incorrect, the server is down, or DNS fails

# Fix: Test the endpoint independently
curl -v -X POST https://api.example.com/webhook/odoo \
    -H "Content-Type: application/json" \
    -d '{"test": true}'

# Check DNS resolution:
nslookup api.example.com

# Check port accessibility:
telnet api.example.com 443

# Common mistakes:
# - HTTP vs HTTPS mismatch
# - Missing /path in URL
# - Trailing slash mismatch (/webhook vs /webhook/)
# - Using localhost (Odoo server can't reach your local machine)

Cause 2: Authentication Failure (401/403)

# The receiving endpoint requires authentication

# Fix: Add authentication headers to the webhook request
# In Automated Action → Execute Python Code:
import requests
import json

url = 'https://api.example.com/webhook/odoo'
headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_API_KEY',
    # Or: 'X-Webhook-Secret': 'shared_secret_here'
}
payload = {
    'event': 'sale_order_confirmed',
    'order_id': record.id,
    'order_name': record.name,
    'amount': record.amount_total,
}

try:
    response = requests.post(url, json=payload, headers=headers, timeout=10)
    response.raise_for_status()
    log(f'Webhook delivered: {response.status_code}', level='info')
except Exception as e:
    log(f'Webhook failed: {e}', level='error')

Cause 3: Timeout

The receiving endpoint takes too long to respond, and Odoo times out.

# Default timeout for requests library is None (wait forever)
# But Odoo worker limits may kill the request

# Fix 1: Set an explicit timeout
response = requests.post(url, json=payload, timeout=30)

# Fix 2: The receiving endpoint should return 200 immediately
# and process the webhook payload asynchronously
# Most webhook receivers follow this pattern:
# 1. Receive POST
# 2. Validate payload
# 3. Return 200 OK
# 4. Process data in background

# Fix 3: Use Odoo cron for async delivery
# Instead of sending synchronously during record save,
# queue the webhook and send via a cron job

Cause 4: SSL Certificate Issues

# Self-signed or expired certificates cause delivery failure

# Error: SSLError: [SSL: CERTIFICATE_VERIFY_FAILED]

# Fix 1: Fix the certificate on the receiving endpoint
# Use Let's Encrypt for free valid certificates

# Fix 2 (development only): Disable SSL verification
response = requests.post(url, json=payload, verify=False, timeout=10)
# WARNING: Never disable SSL verification in production

Cause 5: Payload Too Large

# Sending too much data in the webhook payload

# Fix: Send only essential fields, not entire records
# BAD:
payload = {'order': record.read()[0]}  # Sends everything

# GOOD:
payload = {
    'event': 'order_confirmed',
    'order_id': record.id,
    'order_ref': record.name,
    'customer': record.partner_id.name,
    'amount': record.amount_total,
    'currency': record.currency_id.name,
}

# If the receiver needs more data, they should fetch it via API

Cause 6: Webhook Runs on Every Write

An automated action set to trigger on "On Update" fires on every field change, flooding the endpoint.

# Fix: Add conditions to the automated action
# Automated Action settings:
# Trigger: On Update
# Before Update Filter: [("state", "=", "draft")]
# Apply on: [("state", "=", "sale")]
# This fires only when state changes from draft to sale

# In Python code, check specific fields:
if 'state' not in record._fields:
    return
# Or use env.context to check which fields changed

Building a Reliable Webhook System

# Recommended pattern: Queue + Retry

# 1. Create a webhook queue model:
class WebhookQueue(models.Model):
    _name = 'webhook.queue'
    url = fields.Char(required=True)
    payload = fields.Text(required=True)
    state = fields.Selection([
        ('pending', 'Pending'),
        ('sent', 'Sent'),
        ('failed', 'Failed'),
    ], default='pending')
    attempts = fields.Integer(default=0)
    last_error = fields.Text()

# 2. Automated action queues the webhook instead of sending:
env['webhook.queue'].sudo().create({
    'url': 'https://api.example.com/webhook',
    'payload': json.dumps(payload),
})

# 3. Cron job processes the queue with retries:
# Retry pattern: attempt 1 immediately, 2 after 5 min, 3 after 30 min
# Mark as failed after 3 attempts

Testing Webhooks

# 1. Use a webhook testing service:
# https://webhook.site — gives you a temporary URL to receive webhooks
# Set this URL in your Odoo automated action to verify delivery

# 2. Use ngrok for local testing:
# ngrok http 3000 — creates a public URL for your local server
# Use the ngrok URL as the webhook endpoint

# 3. Check delivery in Odoo logs:
grep -i 'webhook\|delivery.*failed' /var/log/odoo/odoo-server.log | tail -20