WP Webhooks / WordPress Webhooks Guide
/ 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.

~15 min read Updated Apr 2026
#wordpress#webhooks#woocommerce#gravity-forms
/ 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:

  • Identify the trigger eventa form submission, order update, user action
  • Hook into the eventvia add_action() or a plugin's built-in webhook feature
  • Define the endpoint URLthe external system that will receive the payload
  • Shape the payloaddecide what data to send and in what format
  • Send the HTTP requestusing wp_remote_post() or a plugin

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.

WooCommerce order webhook example →
/ 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.

  • No retry mechanism — a single failed request means the event is gone permanently
  • Synchronous execution — the HTTP request blocks the PHP process; slow endpoints slow down your site
  • Timeouts — default timeouts are 5–10 seconds; any API under load will exceed this
  • No persistent queue — events are not stored before dispatch; a PHP crash loses the event entirely
  • No logging — WordPress does not log outbound HTTP requests by default; failures are invisible
  • External API downtime — maintenance windows, rate limits, and outages are normal; your webhook will hit them
  • No replay — even if you notice a failure, you cannot resend the original payload
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:

  • Custom database table — full control, requires schema creation and maintenance
  • Action Scheduler — an open-source library (included with WooCommerce) that provides a persistent job queue with retry logic and a UI
  • WordPress Webhook Plugin — purpose-built delivery layer with queue, retry, logs, and replay out of the box

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.

CapabilityFire-and-forgetProduction-grade
Execution modelSynchronous, blocks PHPBackground worker
Event persistenceNone — lost on crashDatabase queue before dispatch
Retry on failureNeverAutomatic with exponential backoff
Retry logicN/A5xx + network errors only; 4xx → permanent fail
Delivery loggingNonePer-attempt: status, body, duration
Event replayNot possibleManual resend from delivery log
IdempotencyNoneUUID on every event, stable across retries
Performance impactBlocks user-facing requestZero — async dispatch
Failure visibilitySilentLogged, 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 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.

  • Always log every request — HTTP status, response body, and duration on every attempt, success or failure
  • Never rely on a single delivery attempt — assume the first attempt will sometimes fail
  • Use exponential backoff for retries — immediate retries overwhelm already-struggling endpoints
  • Distinguish retryable from permanent failures — 5xx is retryable; 4xx is a configuration problem that needs human attention
  • Store a unique event ID before dispatch — the same ID travels with every retry; receivers use it to deduplicate
  • Validate the response, not just the status code — some endpoints return 200 with an error in the body
  • Keep payloads versioned — add a version field so receivers can handle schema evolution gracefully
  • Monitor queue depth — a growing backlog is an early warning sign of endpoint problems
  • Set up alerts on permanent failures — silent dead-letter queues are the worst of all outcomes

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.

FAQ

Common questions answered.

Have more questions? Browse the full docs or check the API reference.

What are WordPress webhooks? +
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.
Why do WordPress webhooks fail silently? +
The default WordPress webhook 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, the request fails and the event disappears permanently with no record that anything went wrong.
What is the difference between synchronous and asynchronous webhooks in WordPress? +
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.
How do I make WordPress webhooks reliable? +
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.
Does WordPress have built-in webhook support? +
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

Your next automation is
one sentence away.

$ wp plugin install flowsystems-webhook-actions --activate