arraypress/wp-webhooks

Signed webhooks: Stripe-compatible signatures, replay protection and a status-aware retry policy.

Maintainers

Package info

github.com/arraypress/wp-webhooks

Homepage

pkg:composer/arraypress/wp-webhooks

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-08-25 21:29 UTC

This package is auto-updated.

Last update: 2026-08-26 09:51:43 UTC


README

Send webhooks that receivers can verify, and retry the ones worth retrying.

What it does

A webhook that anyone can forge is not a webhook. The signature has to cover a timestamp as well as the body, or an attacker can capture one request and replay it forever.

This uses Stripe's scheme, so receivers already know how to check it and can often reuse code they have. It signs timestamp.payload, sends both in the header, and rejects anything outside a tolerance window on the way back in.

The other half is delivery. Not every failure is worth retrying — a 404 or a 422 will fail identically next time — so the retry policy distinguishes them and backs off on the ones that might recover.

Features

  • Stripe-compatible signatures, so receivers can verify with familiar code
  • Timestamp inside the signed payload, so a captured request cannot be replayed
  • Constant-time comparison, so signatures cannot be brute-forced by timing
  • Supports several active secrets at once, for zero-downtime key rotation
  • Retries only what might succeed — no retry on a 4xx that will never change
  • Exponential backoff that honours a Retry-After header when one is sent
  • Explains why verification failed, without leaking that to the sender

Installation

composer require arraypress/wp-webhooks

Quick start

use ArrayPress\Webhooks\RetryPolicy;
use ArrayPress\Webhooks\Signature;

// Sending.
$payload   = wp_json_encode( [ 'event' => 'order.completed', 'id' => 1001 ] );
$signature = Signature::sign( $payload, $endpoint_secret );

wp_remote_post( $endpoint_url, [
    'headers' => [ 'Webhook-Signature' => $signature ],
    'body'    => $payload,
] );

// Receiving.
$result = Signature::verify( $raw_body, $header, $secret );

if ( ! $result->valid ) {
    status_header( 400 );
    exit;
}

// Failed delivery: worth another go?
if ( RetryPolicy::should_retry( $status, $attempt ) ) {
    $seconds = RetryPolicy::delay( $attempt );
}

Requirements

  • PHP 8.3 or later
  • WordPress 7.1 or later

License

GPL-2.0-or-later