This guide shows how to deploy a webhook gateway in Django that verifies third‑party signatures (e.g., Stripe, GitHub, HubSpot), normalizes events, runs idempotent background jobs, and safely updates WordPress via authenticated REST. It’s designed for multi-tenant SaaS sites and automation-heavy WordPress installs.
Architecture
– Sources: Third-party services send webhooks to a single Django gateway.
– Ingress: Django REST endpoint with HMAC/signature verification, timestamp tolerance, payload caps, and replay protection.
– Queue: Valid events normalized into a canonical schema and enqueued (Celery + Redis or RQ).
– Workers: Idempotent tasks execute business logic and call WordPress via secure REST.
– WordPress: Minimal plugin or functions.php adds authenticated REST routes and caches state.
– Observability: Structured logs, metrics, traces, and a dead-letter queue (DLQ).
Why a Django Gateway in Front of WordPress
– Security: Keeps secrets and signature logic server-side; network allowlisting on a single IP.
– Reliability: Centralizes retries, idempotency, and DLQ handling.
– Performance: Offloads heavy work from WordPress; reduces request time and PHP memory pressure.
– Control: One normalization layer supports many services and tenants.
Django Models (minimal)
– WebhookEvent
– id (uuid), source (str), event_type (str), received_at (datetime)
– raw_body (bytes), headers (json), signature_valid (bool), idempotency_key (str)
– status (received|queued|processed|failed|discarded), attempts (int), error (text)
– JobExecution
– id (uuid), event (fk), task_name (str), status, attempts, started_at, finished_at, error
Ingress Endpoint (Django REST Framework)
– POST /api/webhooks/{source}/ingest
– Steps:
1) Enforce POST, JSON, Content-Length cap (e.g., 512 KB).
2) Read raw body exactly as sent.
3) Validate timestamp header (e.g., Stripe’s t=… within 5 min).
4) Verify HMAC or provider signature with per-tenant secret.
5) Compute idempotency_key from provider event id + source + tenant.
6) Persist WebhookEvent with signature_valid flag and dedupe check.
7) If valid + new, enqueue Celery task; else return 200 for duplicates, 400/401 on invalid.
Example verification (Stripe-style)
– Signature header: Stripe-Signature = t=…,v1=HMAC_SHA256(secret, “{t}.{raw_body}”)
– Reject if timestamp skew > 300s or if computed HMAC != v1.
Pseudo-code (concise)
– Verify
– raw = request.body
– sig_hdr = request.headers.get(“Stripe-Signature”)
– t, v1 = parse(sig_hdr)
– if abs(now – t) > 300: 401
– mac = hex(hmac_sha256(secret, f”{t}.{raw}”))
– if not hmac_compare(mac, v1): 401
– Idempotency
– key = f”{source}:{tenant}:{provider_event_id}”
– if exists_in_store(key): return 200 (duplicate)
– store key in Redis with TTL 24h
– Persist + enqueue
– save WebhookEvent(…)
– celery_delay(event_id)
Normalization
– Map provider payloads to a canonical object:
– actor { id, email }
– subject { id, type }
– action { type, reason }
– data { free-form JSON }
– occurred_at
– tenant_id
– Keep raw payload for auditing.
Celery Task (idempotent)
– Load event by id; exit if already processed.
– Begin idempotency:
– Use Redis SETNX lock “job:{event.id}”
– Execute business logic:
– Transform event → downstream actions.
– Write records to DB; use UPSERTs for repeat events.
– Call WordPress REST with retries.
– Mark processed; release lock.
WordPress Integration
– Auth options:
– Application Passwords (Basic over HTTPS) for server-to-server.
– JWT (recommended if you need scoped tokens and rotation).
– REST route examples:
– POST /wp-json/ai-guy/v1/user-sync
– POST /wp-json/ai-guy/v1/order-event
– Hardening:
– Require server IP allowlist or token signature.
– Validate JSON schema with WP_REST_Request.
– Enforce rate limits via transients or a small options cache.
– Return 202 for async processing; never block on heavy work in PHP.
Minimal WordPress route (pseudo)
– register_rest_route(‘ai-guy/v1’, ‘/user-sync’, [
– ‘methods’ => ‘POST’,
– ‘permission_callback’ => function($request) {
// Verify Authorization header or shared HMAC
return current_user_can(‘manage_options’) || verify_server_token($request);
},
– ‘callback’ => function($request) {
$data = $request->get_json_params();
// validate schema; sanitize
// wp_insert_user or wp_update_user
// set/update user meta
return new WP_REST_Response([‘ok’ => true], 200);
}
])
Django → WordPress Request
– POST with:
– Authorization: Bearer or Basic (App Password)
– X-Request-Id: {uuid}
– X-Signature: HMAC_SHA256(shared_secret, raw_body)
– Retries: exponential backoff (e.g., 1s, 3s, 9s, 27s, jitter), max 5 tries.
– Idempotency: include Idempotency-Key header and handle at WordPress by short-circuiting duplicates.
Security Controls
– Signature verification per provider; rotate secrets quarterly.
– Replay protection with timestamp and nonce storage (Redis SETEX).
– Payload limits and JSON schema validation.
– Network controls: only expose /ingest via a public path; restrict /admin with VPN.
– Secrets in environment variables; no secrets in code or DB exports.
– Audit fields (created_by, source_ip, user_agent).
– PII minimization and encryption at rest if needed.
Observability
– Structured JSON logs: event_id, tenant, source, outcome, latency_ms.
– Metrics:
– webhook.ingest.count, .fail.count
– job.duration.ms, job.retry.count
– wordpress.http.2xx/4xx/5xx
– Tracing: propagate traceparent to WordPress; annotate external calls.
– DLQ: failed events after N retries go to DLQ table + Slack/Email alert.
Performance & Scale
– Keep ingress fast: verify + enqueue only; never call WordPress inline.
– Use gunicorn with async workers or uvicorn + ASGI for bursty loads.
– Redis for locks, idempotency keys, and short-lived state.
– Batch downstream operations when possible (e.g., queue coalescing per user).
– Backpressure: pause workers on WP 5xx storms; circuit breaker per endpoint.
Local Development
– Use ngrok or Cloudflare Tunnel for provider callbacks.
– Seed tenant/provider secrets in .env (never commit).
– Replay fixtures from saved JSON payloads.
– Integration tests:
– Valid signature → 202; invalid → 401
– Duplicate event → 200 no-op
– Worker retry logic on transient WordPress 5xx
– Contract tests for WordPress routes with schema checks.
Example .env (redacted)
– PROVIDER_SECRETS_STRIPE=tenantA:sk_live_xxx,tenantB:sk_live_yyy
– WORDPRESS_BASE_URL=https://example.com
– WORDPRESS_TOKEN=…
– HMAC_SHARED_SECRET=…
Cutover Plan
– Deploy Django gateway behind HTTPS (HSTS).
– Create provider webhook endpoints per tenant: https://api.yourdomain.com/api/webhooks/stripe
– Validate signatures live; mirror events to a non-production DLQ initially.
– Gradually enable WordPress writes per event type; monitor metrics.
– Add dashboards and alerts; document runbooks.
Failure Handling
– Provider 429/5xx at ingress: accept and queue; never call back providers.
– WordPress 4xx: mark failed, do not retry unless fixable (e.g., 409).
– WordPress 5xx/timeouts: exponential retry with jitter up to max window.
– Poison messages: move to DLQ with root-cause tag.
Deliverables checklist
– Django endpoint with signature verification and timestamp tolerance
– Redis idempotency + nonce store
– Celery worker with idempotent jobs and circuit breaker
– WordPress REST routes with auth, schema validation, and idempotent handling
– Observability: logs, metrics, traces, DLQ
– Runbooks: secret rotation, replay, backfills
This pattern keeps webhook security, reliability, and performance centralized, while WordPress remains a clean, fast presentation and light business layer. It’s battle-tested for multi-tenant automations and scales with your traffic.