---
title: "WordPress Webhooks: Setup, Examples, and Why They Fail"
description: "How WordPress webhooks work, why they fail silently in production, and what a reliable setup looks like — with Gravity Forms and WooCommerce examples."
url: "https://wpwebhooks.org/wordpress-webhooks/"
date: "2026-04-09"
---

[WP Webhooks](https://wpwebhooks.org/) / 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 event** — a form submission, order update, user action
-   → **Hook into the event** — via `add_action()` or a plugin's built-in webhook feature
-   → **Define the endpoint URL** — the external system that will receive the payload
-   → **Shape the payload** — decide what data to send and in what format
-   → **Send the HTTP request** — using `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 →](https://wpwebhooks.org/examples/gravity-forms-webhooks/)

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 →](https://wpwebhooks.org/examples/cf7-to-webhook/)

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 →](https://wpwebhooks.org/examples/woocommerce-order-webhook-claude-code/)

/ 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 →](https://wpwebhooks.org/blog/async-webhooks-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 →](https://wpwebhooks.org/blog/wordpress-webhook-retry-replay-system/)

/ 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 →](https://wpwebhooks.org/blog/async-webhooks-wordpress-wp-cron-not-enough/)

### 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.

| 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](https://hoppscotch.io) 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](https://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.

/ 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.

-   [→ Gravity Forms webhook setup — endpoint configuration, field mapping, retry and delivery log](https://wpwebhooks.org/examples/gravity-forms-webhooks/)
-   [→ Contact Form 7 webhook guide — plugin setup, payload format, making it failure-proof](https://wpwebhooks.org/examples/cf7-to-webhook/)
-   [→ All WordPress webhook integration examples →](https://wpwebhooks.org/examples/)
-   [→ Async webhooks in WordPress — moving dispatch out of the PHP request cycle](https://wpwebhooks.org/blog/async-webhooks-wordpress/)
-   [→ WordPress webhook retry and replay system — queue, backoff, and observability](https://wpwebhooks.org/blog/wordpress-webhook-retry-replay-system/)
-   [→ Why WP-Cron is not enough for async webhooks — scheduling gaps and fixes](https://wpwebhooks.org/blog/async-webhooks-wordpress-wp-cron-not-enough/)

/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.

/ Related

-   [→ Gravity Forms webhook example — complete setup with delivery logs and retry](https://wpwebhooks.org/examples/gravity-forms-webhooks/)
-   [→ Contact Form 7 webhook guide — making CF7 integrations failure-proof](https://wpwebhooks.org/examples/cf7-to-webhook/)
-   [→ Why WordPress webhooks silently fail in production — every failure mode mapped](https://wpwebhooks.org/blog/why-wordpress-webhooks-silently-fail-in-production/)
-   [→ Async webhooks in WordPress — architecture and implementation](https://wpwebhooks.org/blog/async-webhooks-wordpress/)
-   [→ Why WP-Cron is not enough — the scheduling gap that causes silent failures](https://wpwebhooks.org/blog/async-webhooks-wordpress-wp-cron-not-enough/)
-   [→ WordPress webhook retry and replay system — queue design and backoff strategy](https://wpwebhooks.org/blog/wordpress-webhook-retry-replay-system/)

/Ready

## Your next automation is  
one sentence away.

[Install Plugin→](https://wordpress.org/plugins/flowsystems-webhook-actions/) [See the Plugin →](https://wpwebhooks.org/wordpress-webhook-plugin/)

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

## Structured data

```json
{"@context":"https://schema.org","@type":"Article","headline":"WordPress Webhooks: Setup, Examples, and Why They Fail","description":"How WordPress webhooks work, why they fail silently in production, and what a reliable setup looks like — with Gravity Forms and WooCommerce examples.","datePublished":"2026-04-09","dateModified":"2026-04-09","author":{"@type":"Person","name":"Mateusz Skorupa","url":"https://wpwebhooks.org/about/"},"publisher":{"@type":"Organization","name":"WP Webhooks","url":"https://wpwebhooks.org","sameAs":["https://flowsystems.pl"]},"url":"https://wpwebhooks.org/wordpress-webhooks/","image":"https://wpwebhooks.org/wordpress-webhooks/og_image.jpg","keywords":["wordpress webhooks","wordpress webhook setup","gravity forms webhook","contact form 7 webhook","woocommerce webhooks","reliable webhooks wordpress","webhook retry","async webhooks wordpress"]}

{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"WP Webhooks","item":"https://wpwebhooks.org/"},{"@type":"ListItem","position":2,"name":"WordPress Webhooks Guide","item":"https://wpwebhooks.org/wordpress-webhooks/"}]}

{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{"@type":"Question","name":"What are WordPress webhooks?","acceptedAnswer":{"@type":"Answer","text":"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."}},{"@type":"Question","name":"Why do WordPress webhooks fail silently?","acceptedAnswer":{"@type":"Answer","text":"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."}},{"@type":"Question","name":"What is the difference between synchronous and asynchronous webhooks in WordPress?","acceptedAnswer":{"@type":"Answer","text":"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."}},{"@type":"Question","name":"How do I make WordPress webhooks reliable?","acceptedAnswer":{"@type":"Answer","text":"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."}},{"@type":"Question","name":"Does WordPress have built-in webhook support?","acceptedAnswer":{"@type":"Answer","text":"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."}}]}
```
