WP Manifestindependent plugin directory
manifest / integrations / wp-webhook-relay

WP Webhook Relay

Reliable outbound webhook system for WordPress — queued delivery with exponential backoff, HMAC signing, delivery logs, and a REST management API

by wp-coding-agent · github.com/wp-coding-agent/wp-webhook-relay

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/wp-coding-agent/wp-webhook-relay/archive/refs/heads/main.zip

Reliable outbound webhook delivery for WordPress — queued processing with exponential backoff, HMAC-SHA256 signing, delivery logs, and a full REST management API.

The Problem

WordPress has do_action() for internal events, but no built-in way to reliably notify external systems. Sending HTTP requests inside hooks is fragile: timeouts block page loads, failures are silent, and there's no retry mechanism.

WP Webhook Relay adds Stripe-quality webhook infrastructure to any WordPress site.

Features

  • Queued delivery — events are queued in a DB table and processed asynchronously via WP-Cron
  • Exponential backoff — failed deliveries retry at increasing intervals (1min → 5min → 25min → 2hr → 10hr) with jitter
  • HMAC signing — every payload is signed with SHA-256 HMAC; receivers can verify authenticity
  • Delivery logging — every attempt is logged with status code, response time, and truncated response body
  • REST API — full CRUD for subscriptions, delivery history, retry, and event replay
  • DB lockingGET_LOCK() prevents overlapping cron runs from double-processing
  • Developer API — clean static facade: WebhookRelay::dispatch() and WebhookRelay::subscribe()

Installation

composer require wp-coding-agent/wp-webhook-relay

Activate the plugin — tables are created automatically via dbDelta.

Usage

Dispatching Events

use WebhookRelay\WebhookRelay;

// In your plugin or theme:
add_action('transition_post_status', function ($new, $old, $post) {
    if ($new === 'publish' && $old !== 'publish') {
        WebhookRelay::dispatch('post.published', [
            'post_id' => $post->ID,
            'title'   => $post->post_title,
            'url'     => get_permalink($post),
            'author'  => get_the_author_meta('display_name', $post->post_author),
        ]);
    }
}, 10, 3);

Registering Subscriptions

// Via PHP:
WebhookRelay::subscribe(
    'https://api.example.com/webhooks/wordpress',
    ['post.published', 'user.created'],
    'whsec_your_signing_secret'
);

// Via REST API:
// POST /wp-json/webhook-relay/v1/subscriptions
// {"url": "https://...", "events": ["post.published"], "secret": "optional"}

Verifying Signatures (Receiver Side)

// On the receiving server:
$payload   = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$secret    = 'whsec_your_signing_secret';

$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    die('Invalid signature');
}

$data = json_decode($payload, true);
// Process $data['event'], $data['data'], etc.

Webhook Envelope Format

{
  "event": "post.published",
  "timestamp": "2026-03-22T12:00:00+00:00",
  "delivery_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "post_id": 42,
    "title": "Hello World"
  }
}

REST API

Method Endpoint Description
GET /webhook-relay/v1/subscriptions List subscriptions
POST /webhook-relay/v1/subscriptions Create subscription
DELETE /webhook-relay/v1/subscriptions/{id} Delete subscription
GET /webhook-relay/v1/deliveries List queue entries (filterable)
POST /webhook-relay/v1/deliveries/{id}/retry Retry a failed delivery
POST /webhook-relay/v1/replay Re-dispatch an event
GET /webhook-relay/v1/log View delivery attempt logs

All endpoints require manage_options capability.

WP-CLI

wp webhook list                    # List all subscriptions
wp webhook stats                   # Queue status breakdown
wp webhook process                 # Process queue immediately
wp webhook replay post.published --payload='{"post_id":42}'

License

GPL-2.0-or-later