Webhook Configuration

This guide explains how to configure webhooks in your Cloudflare dashboard to automatically update image status when uploads complete, and documents the request/response contract that WebhookView enforces on the Django side.

Note

The signature-validation gate is enforced when CLOUDFLARE_IMAGES["WEBHOOK_SECRET"] is set: requests without a valid signature header are rejected before the body is parsed. See Response Codes below for the full status-code matrix.

What are Webhooks?

Webhooks allow Cloudflare to automatically notify your Django application when image uploads are completed or failed.

Note

Webhooks are currently only supported for direct creator uploads.

Step-by-Step Webhook Configuration

1. Access Cloudflare Dashboard

  1. Go to https://dash.cloudflare.com/

  2. Log in to your Cloudflare account

  3. Select your account

3. Create Webhook Destination

  1. From the Webhooks card, select “Create”

  2. Fill in the webhook details:

    • Name: Give your webhook a descriptive name (e.g., “Django Images Webhook”)

    • URL: https://yourdomain.com/cloudflare-images/api/webhook/

    • Secret (Optional but recommended): Enter your webhook secret if configured

  3. Click “Save and Test”

  4. The new webhook will appear in the Webhooks card

4. Create Notification

  1. Go to “Notifications” > “All Notifications”

  2. Click “Add”

  3. Under the list of products, locate “Images” and select “Select”

  4. Configure the notification:

    • Name: Give your notification a descriptive name

    • Description: Optional description

    • Webhooks: Select the webhook you created in step 3

  5. Click “Save”

5. Webhook Events

The webhook will be triggered for these events:

  • Image Upload Complete - When a direct creator upload succeeds

  • Image Upload Failed - When a direct creator upload fails

Important

Webhooks are only triggered for direct creator uploads, not for regular API uploads.

Django Configuration

End-to-end Django setup is five steps. Run them in order on a fresh project; each step is independent on a project that already has the toolkit installed.

Step 1: Install the package

pip install django-cloudflareimages-toolkit

Then add "django_cloudflareimages_toolkit" to INSTALLED_APPS and run python manage.py migrate so the CloudflareImage and ImageUploadLog tables are created. The webhook view writes status changes into these tables.

Step 2: Configure the webhook secret

Generate a high-entropy secret (e.g. python -c 'import secrets; print(secrets.token_urlsafe(32))') and store it in your settings. The same value must be entered in the Cloudflare dashboard in step 5.

# settings.py
import os

CLOUDFLARE_IMAGES = {
    "ACCOUNT_ID": os.environ["CLOUDFLARE_ACCOUNT_ID"],
    "API_TOKEN":  os.environ["CLOUDFLARE_API_TOKEN"],
    "WEBHOOK_SECRET": os.environ["CLOUDFLARE_WEBHOOK_SECRET"],
}

Important

Setting WEBHOOK_SECRET is what enforces signature validation on inbound webhook requests. Without it, every well-formed payload is accepted. See Response Codes for the gate contract.

Step 3: Mount the URLs

# urls.py
from django.urls import include, path

urlpatterns = [
    # ... your other URLs ...
    path("cloudflare-images/", include("django_cloudflareimages_toolkit.urls")),
]

This exposes the webhook at https://yourdomain.com/cloudflare-images/api/webhook/ along with the rest of the toolkit’s REST endpoints. The view is CSRF-exempt by default, so you do not need to add it to CSRF_EXEMPT_PATHS or wrap it.

Step 4: React to status changes

The bundled WebhookView updates the CloudflareImage row’s status field whenever Cloudflare reports a transition. To react to those changes in your own code, hook Django’s post_save signal on CloudflareImage and branch on the new status:

# apps.py
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = "myapp"

    def ready(self):
        from django.db.models.signals import post_save
        from django_cloudflareimages_toolkit.models import CloudflareImage
        from . import handlers
        post_save.connect(handlers.on_image_status_change, sender=CloudflareImage)

# handlers.py
def on_image_status_change(sender, instance, created, **kwargs):
    if instance.status == "uploaded":
        # Image is live — generate thumbnails, send notifications, etc.
        notify_owner(instance)
    elif instance.status == "failed":
        # Cloudflare rejected the upload — reset the user's UI state
        mark_upload_failed(instance)

If you’d rather process events without going through the database row, subclass WebhookView and override post; see apps/api/v1/images/cloudflare_views.py in pacficient-labs/django-cloudflareimages-toolkit for a working example.

Step 5: Configure Cloudflare to deliver

Follow the Step-by-Step Webhook Configuration above to register your public URL (https://yourdomain.com/cloudflare-images/api/webhook/) and the same secret you stored in step 2. Cloudflare will start delivering events on the next direct-creator upload.

Alternative: Using Cloudflare API

You can also configure webhooks programmatically using the Cloudflare API. This involves two steps:

Step 1: Create Webhook Destination

curl -X POST "https://api.cloudflare.com/client/v4/accounts/{account_id}/notification_destinations" \
  -H "Authorization: Bearer {api_token}" \
  -H "Content-Type: application/json" \
  --data '{
    "name": "Django Images Webhook",
    "type": "webhook",
    "webhook": {
      "url": "https://yourdomain.com/cloudflare-images/api/webhook/",
      "secret": "your-webhook-secret"
    }
  }'

Step 2: Create Notification Policy

curl -X POST "https://api.cloudflare.com/client/v4/accounts/{account_id}/alerting/v3/policies" \
  -H "Authorization: Bearer {api_token}" \
  -H "Content-Type: application/json" \
  --data '{
    "name": "Images Upload Notifications",
    "description": "Notifications for Cloudflare Images uploads",
    "enabled": true,
    "alert_type": "images_upload_complete",
    "mechanisms": {
      "webhooks": ["webhook-destination-id-from-step-1"]
    }
  }'

Note

Replace webhook-destination-id-from-step-1 with the ID returned from the first API call.

Response Codes

WebhookView.post returns the following statuses. Configure your upstream monitoring on the 4xx path for caller errors and the 5xx path for genuine outages; that split is meaningful here.

Status

When

200 OK

Payload validated, processed, image found and updated.

400 Bad Request

Body wasn’t valid JSON, or body parsed but failed WebhookPayloadSerializer validation. The caller sent a malformed payload.

401 Unauthorized

WEBHOOK_SECRET is configured and the request is missing an X-Signature / X-Cloudflare-Signature header, or the supplied signature failed HMAC verification.

404 Not Found

Payload was valid but referenced a cloudflare_id not present in the local CloudflareImage table.

500 Internal Server Error

Reserved for genuinely unexpected failures inside cloudflare_service.process_webhook. A traceback is logged via logger.exception.

Changed in version 1.0.11: The signature gate is now enforced when WEBHOOK_SECRET is set (was previously bypassed when the header was absent), and malformed payloads now return 400 instead of being misclassified as 500. See the v1.0.11 release notes for the underlying bug analysis.

Webhook Payload Examples

Upload Complete

{
  "id": "2cdc28f0-017a-49c4-9ed7-87056c83901",
  "uploaded": "2024-01-01T12:00:00.000Z",
  "variants": [
    "https://imagedelivery.net/Vi7wi5KSItxGFsWRG2Us6Q/2cdc28f0-017a-49c4-9ed7-87056c83901/public",
    "https://imagedelivery.net/Vi7wi5KSItxGFsWRG2Us6Q/2cdc28f0-017a-49c4-9ed7-87056c83901/thumbnail"
  ],
  "metadata": {
    "key": "value"
  },
  "requireSignedURLs": true
}

Upload Failed

{
  "id": "2cdc28f0-017a-49c4-9ed7-87056c83901",
  "error": "Image processing failed",
  "timestamp": "2024-01-01T12:00:00.000Z"
}

Troubleshooting

Common Issues

  1. Webhook not receiving requests

    • Check that your Django server is accessible from the internet

    • Verify the webhook URL is correct

    • Check firewall settings

  2. Authentication errors

    • Verify your webhook secret matches in both Cloudflare and Django

    • Check that the secret is properly configured

  3. SSL/TLS errors

    • Ensure your webhook URL uses HTTPS

    • Check that your SSL certificate is valid

Testing Webhooks Locally

For local development, you can use tools like ngrok to expose your local server:

# Install ngrok
npm install -g ngrok

# Expose your local Django server
ngrok http 8000

# Use the ngrok URL in your webhook configuration
# Example: https://abc123.ngrok.io/cloudflare-images/api/webhook/

Webhook Logs

Check your Django logs for webhook activity:

# In your Django settings.py
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'file': {
            'level': 'INFO',
            'class': 'logging.FileHandler',
            'filename': 'cloudflare_webhooks.log',
        },
    },
    'loggers': {
        'django_cloudflareimages_toolkit': {
            'handlers': ['file'],
            'level': 'INFO',
            'propagate': True,
        },
    },
}

Security Considerations

  1. Always use HTTPS for webhook URLs

  2. Configure webhook secrets to verify request authenticity

  3. Validate payload structure before processing

  4. Rate limit webhook endpoints if necessary

  5. Log webhook activity for monitoring and debugging

Monitoring Webhook Health

You can monitor webhook health through:

  1. Django Admin: View webhook logs in the admin interface

  2. Cloudflare Dashboard: Check webhook delivery status

  3. Application Logs: Monitor webhook processing in your logs

  4. Custom Metrics: Track webhook success/failure rates

Next Steps

After configuring webhooks:

  1. Test with a sample image upload

  2. Monitor the Django admin for automatic status updates

  3. Check logs to ensure webhooks are being processed correctly

  4. Set up monitoring and alerting for webhook failures

For more information, see the Cloudflare Images API documentation.