API Documentation

Webhooks

Deliveries & Security

Payload format

Every delivery is a JSON POST with the same envelope. The data block is rebuilt from the live object at delivery time, so a retry never carries stale figures:

POST /your-endpoint HTTP/1.1
Content-Type: application/json
User-Agent: CouponAffiliates-Webhook/8.2.0
X-WCUsage-Event: payout.paid
X-WCUsage-Delivery: whd_66b4a1e2c3d4f5.12345678
X-WCUsage-Signature: t=1786457100,v1=5f8a2b...

{
  "event": "payout.paid",
  "created": "2026-08-07T14:05:00+00:00",
  "site": "https://example.com",
  "data": {
    "payout_id": 321,
    "user_id": 1456,
    "coupon_id": 8338,
    "amount": 40.46,
    "method": "PayPal",
    "method_type": "paypal",
    "status": "paid",
    "date": "2026-08-07T09:00:00",
    "date_paid": "2026-08-07T14:04:58"
  }
}
Envelope fieldBeschreibung
eventThe webhook event name, e.g. payout.paid.
createdWhen the delivery was built, RFC 3339 in UTC.
siteThe sending site's home URL — useful when one receiver serves several stores.
dataEvent-specific payload; shapes below.

Payload shapes by event

Event(s)data fields
referral.createdorder_id, coupon, user_id, commission
commission.added, commission.removed, commission.mla_added, commission.mla_removedcoupon_id, coupon, user_id, note, order_id (parsed from the note; null when there is none)
payout.requested, payout.paid, payout.reversed, payout.cancelledpayout_id, user_id, coupon_id, amount, method, method_type, status, date, date_paid
registration.created, registration.accepted, registration.declinedregistration_id, user_id, coupon_code, status, type, date
affiliate.createduser_id, coupon, coupon_id, coupon_ids (every coupon they now hold)
affiliate.payout_details_updateduser_id, method_type, has_details
directlink.createddirectlink_id, coupon_id, coupon, user_id, website, campaign, status
mla.invite_createdinvite_id, user_id, status, date
mla.sub_registereduser_id, parent_user_id, coupon
reward.earned, campaign.createdobject_id, info
ping (test delivery)message
Hinweis
Payloads never carry customer personal data, payout destination details, or the email address of a person who is not a user of the site. mla.invite_created, for example, deliberately omits the invitee's email address. Add fields for your own trusted receiver with the wcusage_api_webhook_payload filter.

Verifying signatures

Each webhook has a secret (whsec_…), shown in the admin UI and returned once on creation through the API. Every delivery is signed with HMAC-SHA256 over "<timestamp>.<raw body>":

X-WCUsage-Signature: t=<unix timestamp>,v1=<hex hmac>

Verify it before trusting anything in the payload:

<?php
$secret = 'whsec_your_webhook_secret';
$body   = file_get_contents( 'php://input' );
$header = $_SERVER['HTTP_X_WCUSAGE_SIGNATURE'] ?? '';

$parts = [];
foreach ( explode( ',', $header ) as $pair ) {
    [ $k, $v ] = array_pad( explode( '=', $pair, 2 ), 2, '' );
    $parts[ trim( $k ) ] = trim( $v );
}

$expected = hash_hmac( 'sha256', ( $parts['t'] ?? '' ) . '.' . $body, $secret );

if ( ! hash_equals( $expected, $parts['v1'] ?? '' ) ) {
    http_response_code( 401 );
    exit; // signature mismatch
}

if ( abs( time() - (int) ( $parts['t'] ?? 0 ) ) > 300 ) {
    http_response_code( 401 );
    exit; // replayed or stale delivery
}

http_response_code( 200 );
// then process json_decode( $body, true ) out of band
Tip
Always compare with a constant-time function (hash_equals), sign over the raw body rather than a re-encoded copy, and reject timestamps older than a few minutes to prevent replay.

Retries and automatic disabling

  • Deliveries are queued asynchronously, so your endpoint is never in the critical path of a checkout or an admin action.
  • A failed delivery is retried up to 5 attempts with exponential backoff: roughly 1 minute, 4 minutes, 16 minutes, then about an hour.
  • Nach 25 consecutive failures the endpoint is automatically disabled. Re-enable it from the admin page or with a PATCH request; either resets the failure counter and clears the last error.
  • Delivery is at-least-once. Duplicates are possible, so use X-WCUsage-Delivery to deduplicate if your handler is not idempotent.
  • Ordering is not guaranteed. Two events raised close together may arrive in either order, and a retried delivery may land after a newer one.
  • The last failure message and the failure count are visible on the admin page and in GET /webhooks.

Tune the two limits with the wcusage_api_webhook_max_attempts und wcusage_api_webhook_max_failures filters.