/ Complete Guide — Setup, Examples & Reliability

WordPress Webhooks:
Setup, Examples, and Why They Fail

Everything you need to understand WordPress webhooks — how they work under the hood, how to set them up, where they break in production, and what a reliable delivery setup actually looks like.

/ Fundamentals

How WordPress webhooks actually work

A WordPress webhook is an outbound HTTP POST request sent automatically when something happens inside WordPress. A form gets submitted. An order status changes. A new user registers. WordPress fires an action hook, your code catches it, and sends a JSON payload to a URL you configured.

That's the happy path. In practice, the implementation details determine whether your webhook is reliable or a ticking time bomb.

Most webhook setups in WordPress rely on a single WordPress function called synchronously during the PHP request that triggered the event:

typical wordpress webhook call
add_action( 'woocommerce_order_status_completed', function( $order_id ) { wp_remote_post( 'https://your-crm.example.com/webhook', [ 'headers' => [ 'Content-Type' => 'application/json' ], 'body' => wp_json_encode( [ 'order_id' => $order_id ] ), 'timeout' => 5, ] ); // Result never checked. No retry. No log. If it fails, no one knows. } );

This runs inside the same PHP process that completed the order. The HTTP request is sent synchronously — the PHP thread sits and waits for a response before doing anything else. The user's browser is waiting too.

If the CRM is slow, the checkout is slow. If the CRM is down, the request times out after 5 seconds and the event is permanently lost. There is no queue. There is no retry. WordPress has already moved on.

The key insight: WordPress was built to render pages, not to deliver events reliably. Using it as an event delivery system without adding the right infrastructure is the source of most webhook failures.

/ Setup

Setting up webhooks in WordPress

There is no single "WordPress webhooks" feature. How you set one up depends on where the event comes from. The general flow is always the same:

Plugins like Gravity Forms, Contact Form 7, and WooCommerce all have their own webhook/notification systems with varying levels of configurability. Custom plugins and themes typically use add_action() hooks directly.

Gravity Forms → Webhook

Gravity Forms

Gravity Forms has a built-in Webhooks Add-On. Configure endpoint URL, request format, and field mapping per-form. No custom code needed for basic setups.

Full setup guide + reliability fix →
Contact Form 7 → Webhook

Contact Form 7

CF7 requires a plugin or custom code to send webhooks. The CF7 to Webhook plugin is the common solution — it adds endpoint configuration to each form.

Full CF7 webhook example →
WooCommerce → Webhook

WooCommerce

WooCommerce has a native webhook system under WooCommerce → Settings → Advanced → Webhooks. It covers order, product, customer, and coupon events. Delivery is handled via Action Scheduler, so it is asynchronous with basic retry support — but logging and replay are limited compared to a dedicated delivery layer.

/ Use cases

Common use cases for WordPress webhooks

Most WordPress webhook use cases fall into one of these four categories. Notice that all of them are business-critical flows — which makes silent failures a serious problem.

01 / LEADS

Send leads to CRM

Form submissions — Gravity Forms, CF7, WPForms — pushed to HubSpot, Pipedrive, or a custom API. A missed event means a missed lead. No second chance.

02 / AUTOMATION

Trigger automation workflows

WordPress as an event source for n8n, Make, Zapier, or Pipedream. Order placed → Slack notification → fulfillment ticket created. One dropped webhook breaks the whole chain.

03 / ORDERS

Sync WooCommerce orders

Push order events to ERP systems, 3PL providers, accounting platforms. Order fulfillment depends on the event arriving. A missed delivery means a missed shipment.

04 / INTEGRATIONS

Custom API integrations

Push WordPress events to Notion, Airtable, Slack, custom SaaS APIs. Any HTTP endpoint can be a webhook consumer — making WordPress a flexible event emitter for your entire stack.

If any of these flows matter to your business, you cannot afford fire-and-forget delivery. The question is not if a webhook will fail — it's when, and what happens next.

/ The problem

Why WordPress webhooks fail silently in production

Most WordPress webhook setups are "fire and forget" — which really means "hope it works." When conditions are perfect, they do. In production, conditions are never perfect.

The silence is the failure mode.
No error in the WordPress admin. No email. No alert. The order shows as completed, the form shows as submitted — but your CRM, ERP, or automation platform never received the event.

These are not edge cases. They are the normal operating conditions of any production environment. External APIs have planned maintenance. Networks drop packets. Rate limits kick in. A webhook system that can't handle any of these is not production-ready.

/ Consequences

What actually happens when a webhook fails

The downstream effects of a missed webhook depend entirely on what the webhook was doing. They range from annoying to genuinely damaging.

LEADS

Lost leads

A contact form submission that never reaches your CRM is a prospect who falls through the cracks. Sales teams never see them. Follow-ups never happen.

ORDERS

Missed order events

A WooCommerce "order completed" event that never reaches your ERP means an order that never gets fulfilled, or an invoice that never gets created.

AUTOMATION

Broken automation chains

Automation workflows in n8n, Make, or Zapier are only as reliable as the trigger event. One dropped webhook breaks the entire downstream sequence silently.

VISIBILITY

Zero visibility

The hardest part: you often won't know something went wrong until a customer complains or you manually audit records. By then, recovery is difficult.

The consistent pattern across all these failures: the problem is discovered days later, when someone notices a discrepancy. By then, PHP logs have rotated, the original payload is gone, and there is no forensic record to work with.

/ Architecture

Synchronous vs asynchronous webhooks

The core architectural decision in webhook delivery is when the HTTP request gets sent: during the PHP request that triggered the event (synchronous), or in a background process (asynchronous).

Synchronous — inline
User submits form WordPress fires action hook wp_remote_post() called PHP thread waits for response API slow → user waits too API down → request fails, event lost
Asynchronous — queued
User submits form WordPress fires action hook Event stored in queue (fast, safe) Response returned to user immediately Background worker dispatches event Failure? Auto-retry with backoff

Async delivery gives you two things that synchronous delivery can never provide:

  • Reliability — the event is stored before dispatch, so it survives any failure in the delivery path
  • Performance — the user-facing request is never blocked by an outbound HTTP call to a slow external API

The tradeoff is latency — async delivery introduces a small delay between event and delivery (typically seconds to a minute). For most use cases, this is completely acceptable. For use cases where it isn't, synchronous delivery can still be used selectively while still adding logging and idempotency.

For a detailed walkthrough of async dispatch architecture in WordPress, see Async Webhooks in WordPress →

/ The fix

What a reliable webhook setup looks like

Reliable webhook delivery is not about writing better PHP. It is about adding the right infrastructure around the HTTP call. A production-grade setup needs five components:

01 / QUEUE

Persistent queue

Store events in the database before attempting delivery. This decouples event capture from dispatch — the event survives even if the delivery process crashes mid-flight.

02 / RETRY

Automatic retry with backoff

Retry failed deliveries automatically. Space retries further apart with each attempt: 1 min → 2 min → 4 min → 8 min. Retry 5xx and network errors; fail permanently on 4xx — those are configuration problems, not transient failures.

03 / ASYNC

Background dispatch

Send events from a background worker process, not during the user-facing request. This removes the performance risk and allows the worker to handle long-running deliveries safely.

04 / LOGS

Delivery logs

Log every attempt: timestamp, endpoint, HTTP status, response body, duration. Logs are what turn invisible failures into diagnosable problems — and they're the foundation for replay.

05 / REPLAY

Replay capability

After an outage or configuration problem, you need to be able to resend failed events from the delivery log. Without replay, any failure window means permanent data loss.

These five components are not optional extras. Each one addresses a specific failure mode. Remove any one of them and you reintroduce that failure mode. Deep dive: building a retry and replay system for WordPress →

/ Limitations

WordPress limitations and workarounds

WordPress was not designed as event infrastructure, and that shows in several ways. Understanding the limitations helps you work around them effectively.

WP-Cron is not a real scheduler

WP-Cron — WordPress's built-in job scheduler — does not run on a time-based schedule. It fires on page load. If your site receives no traffic overnight, WP-Cron does not run. Events that should have been retried at 2am sit in the queue until morning.

The reliable fix: disable WP-Cron's page-load trigger and replace it with a real system cron job that runs every minute. Full breakdown of WP-Cron limitations and fixes →

PHP execution model

PHP is stateless and request-bound. Each page load boots PHP fresh, runs, and terminates. There is no persistent background process. This means background jobs must be triggered externally — you cannot have a long-running PHP worker without additional infrastructure.

On shared hosting, execution time limits (often 30–60 seconds) can kill a batch processing job mid-run, leaving some events in a "processing" limbo state. Your queue needs a stuck-job detection mechanism to recover from this.

No built-in queue system

WordPress core has no persistent job queue. The common solutions are:

Action Scheduler is a solid general-purpose queue, but it requires custom integration code to handle all the delivery concerns (status tracking, per-attempt logging, idempotency keys). A purpose-built solution handles these for you.

/ Comparison

Fire-and-forget vs production-grade delivery

Each row in this table represents a specific failure mode. The fire-and-forget column is missing all the things that let you detect, recover from, and prevent delivery failures.

Capability Fire-and-forget Production-grade
Execution model Synchronous, blocks PHP Background worker
Event persistence None — lost on crash Database queue before dispatch
Retry on failure Never Automatic with exponential backoff
Retry logic N/A 5xx + network errors only; 4xx → permanent fail
Delivery logging None Per-attempt: status, body, duration
Event replay Not possible Manual resend from delivery log
Idempotency None UUID on every event, stable across retries
Performance impact Blocks user-facing request Zero — async dispatch
Failure visibility Silent Logged, queryable, alertable
/ Debugging

Debugging WordPress webhook delivery issues

Debugging a fire-and-forget webhook implementation is painful: there is nothing to debug with. A production-grade setup makes debugging straightforward.

Start with delivery logs

If you have per-attempt logs, debugging is usually a five-second task: find the event, look at the HTTP status and response body, and you know exactly what went wrong. A 401 means authentication. A 400 means payload format. A 503 means the API was down.

Inspect the payload

The most common source of 400 errors is a payload structure mismatch — the receiving API expects a different field name, nesting, or data type. Log the full request body on every attempt. When something breaks, you can inspect exactly what was sent.

Test your endpoint independently

Before debugging the WordPress side, verify the endpoint works. Use Hoppscotch or curl to send a test payload directly to the endpoint URL and confirm it responds correctly. This isolates whether the problem is WordPress-side or endpoint-side.

Retry manually

If your setup supports it, manually replay the failed event after fixing the underlying cause. This is the only way to recover events without asking the user to resubmit a form or re-trigger an order event.

Use request.bin or webhook.site during development

webhook.site gives you a temporary URL that logs every incoming request in full detail — headers, body, timing. Invaluable for inspecting exactly what WordPress is sending before you point it at a real endpoint.

/ Best practices

Best practices for WordPress webhooks

Most of these follow directly from the failure modes described above. They're not optional for anything you run in production.

The single highest-leverage change you can make: add logging before anything else. You cannot fix what you cannot see. Even if you don't implement the full queue-and-retry system immediately, logging every delivery attempt gives you the visibility to understand where and when things are failing.

/ Examples

Real examples: step-by-step guides

These guides walk through complete webhook setups — including the reliability layer. Not just "how to configure the webhook URL," but how to handle failures, log deliveries, and recover from outages.

/ FAQ

Common questions answered

WordPress webhooks are outbound HTTP POST requests triggered automatically when specific events occur inside WordPress — a form submission, an order status change, a new user registration. They send a JSON payload to a URL you configure, allowing WordPress to push data to external systems in near real time.
The default implementation uses wp_remote_post() called synchronously during page execution. There is no retry mechanism, no persistent queue, and no logging. If the receiving API is unavailable — timeout, 500 error, rate limit — the request fails and the event disappears permanently with no record that anything went wrong.
Synchronous webhooks send the HTTP request inline during the PHP execution that triggered the event. If the request is slow or fails, the user's page is affected and the event may be lost. Asynchronous webhooks store the event in a queue and dispatch it in a background worker process. This decouples delivery from the user request, enables retries, and provides full delivery logging. See the async webhooks guide →
Reliable delivery requires: a persistent queue (store events before dispatch), automatic retry with exponential backoff (for 5xx and network errors), delivery logging (every attempt recorded with status and response), replay capability (resend failed events manually), and idempotency keys (unique event UUIDs for safe deduplication by receivers). The WordPress Webhook Plugin implements all of these out of the box.
WordPress core has no built-in webhook system. WooCommerce ships with webhook support for order, product, customer, and coupon events — delivered via Action Scheduler with basic retry. Gravity Forms and Contact Form 7 require either a dedicated add-on or plugin. For full delivery logging, replay capability, and idempotency across all event types, you need either a dedicated webhook plugin or custom code implementing the complete delivery layer.

Ready to make your WordPress webhooks reliable?

The WordPress Webhook Plugin adds a production-grade delivery layer — persistent queue, automatic retry, full delivery logs, and replay — without changing how your existing integrations work. Free and open source.