CreditKit Pro webhooks

CreditKit Pro delivers 18 webhook event types to any public http or https endpoint. Each delivery is a JSON POST signed with HMAC-SHA256 and stamped with envelope version 2025-01-01. A failed delivery is retried up to four times on a fixed backoff ladder, and an endpoint that exhausts all five attempts is disabled automatically. Delivery runs in the background through Action Scheduler.

Webhooks are managed at Credit System → API & Webhooks, and over REST at /wp-json/pcs/v1/webhooks. The {id} segment of every webhook route is a UUID version 4 string of exactly 36 characters, matching the route pattern [a-f0-9\-]{36}.

The 18 event types

EventFires when
credit.addedAny addition to a balance: bundle purchase, admin grant or refund restoration.
credit.spentAny debit from a balance: credit-paid checkout or refund claw-back.
credit.expiredThe expiry job removes credits past their expiry date.
bundle.purchasedA credit bundle order completes and the credits are granted.
order.paidAn order is settled through the Pay with Credits gateway.
refund.restoredAn admin issues goodwill store credit from the order edit screen.
fraud.flaggedThe fraud engine scores a transaction at or above the high threshold.
fraud.user_suspendedThe fraud engine suspends a customer account.
user.blockedAn admin or an automated rule blocks a customer.
user.unblockedA customer block is lifted.
user.unsuspendedA suspended customer is reinstated, including by the cooldown job.
user.login_lockedLogin protection locks an account after repeated failures.
ip.blockedAn IP address is added to the blocklist.
ip.unblockedAn IP address block is lifted.
balance.drift_detectedReconciliation finds a balance that disagrees with the ledger.
backup.failedA scheduled credit-data backup fails.
tier.downgradedThe CreditKit Pro licence tier for the site is downgraded.
webhook.testAn admin presses Test on a webhook row. This event is hidden from the subscription form.

Seventeen of those 18 are subscribable, because webhook.test is an internal ping CreditKit Pro sends on demand. tier.downgraded refers to the licence tier of the CreditKit Pro installation and carries no customer data. A nineteenth event, badge.earned, joins the registry only while the Rewards gamification toggle is on, and that toggle ships off.

Legacy event names from before 2.2.0 still work at subscribe time. CreditKit Pro resolves names such as user.suspension_cleared and security.fraud_alert to their canonical equivalents, then stores and delivers the canonical name only.

Envelope and headers

{
  "api_version": "2025-01-01",
  "event_id": "6f1c2b7e-6d1c-4d0a-9f4a-2a1a5f3f9c11",
  "event": "credit.spent",
  "webhook_id": "1b4e28ba-2fa1-11d2-883f-0016d3cca427",
  "timestamp": 1774180800,
  "data": {
    "user_id": 42,
    "amount": 12,
    "new_balance": 88,
    "description": "Order #1043",
    "usage_type": "general",
    "context": "order"
  }
}
HeaderValue
Content-Typeapplication/json
X-PCS-Signaturesha256= followed by the HMAC hex digest of the raw body
X-PCS-EventThe canonical event name, for example credit.spent
X-PCS-Event-IdUUID v4, identical across every retry of the same event
X-PCS-API-Version2025-01-01
X-PCS-AttemptAttempt number from 1 to 5

The event_id is generated once per triggering action and reused on every retry, so a receiver can deduplicate on it. Each request times out after 10 seconds.

Verifying the signature

CreditKit Pro shows the webhook secret once, at creation time, and stores only the SHA-256 hex digest of it. Signing uses that stored digest as the HMAC key, so a receiver hashes the secret first and uses the resulting 64-character lowercase hex string as the key.

$payload   = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PCS_SIGNATURE'] ?? '';

// $secret is the value shown once in wp-admin.
$key      = hash('sha256', $secret);
$expected = 'sha256=' . hash_hmac('sha256', $payload, $key);

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

Hash the raw request body. Re-encoding the JSON first changes key order and whitespace, which produces a different digest and a failed comparison.

Which event fires on which refund

CreditKit Pro has three refund mechanisms and each emits a different event. refund.restored covers only the manual goodwill path, so an integration listening for refund.restored alone misses the two automatic mechanisms.

Refund situationWhat happens to the balanceEventMarker in data
An order paid with credits is refunded in wp-admin, over REST or by WP-CLICredits are returned in proportion to the refunded money amountcredit.addedtype is refund
A money-paid order that granted credits from a bundle is refundedGranted credits are clawed back in proportion, clamped at a zero balance by defaultcredit.spentcontext is refund_clawback
An admin issues goodwill store credit from the order edit screenCredits are added at the configured money-to-credits issuance ratecredit.added, then refund.restoredtype is refund_credit on the first event

The goodwill path emits two events for one admin action, so deduplicate on the transaction_id carried in the refund.restored payload.

Retries and auto-disable

A delivery counts as successful on an HTTP status from 200 to 299. Every other status, and every connection error, counts as a failure and schedules the next attempt in the Action Scheduler group pcs-webhooks.

AttemptScheduled
1Immediately, as an async job
260 seconds after attempt 1 fails
3300 seconds after attempt 2 fails
41,800 seconds after attempt 3 fails
57,200 seconds after attempt 4 fails

After attempt 5 fails, CreditKit Pro disables the webhook, records the reason on the row and writes a warning to the WooCommerce log source pcs-webhooks. Re-enabling does not replay the events missed while the endpoint was down.

The delivery log

CreditKit Pro keeps the last 20 delivery records per webhook and drops the oldest when the twenty-first arrives. Each record holds the timestamp, the event name, the event id, the HTTP status, the latency in milliseconds, the attempt number and an error string with IP addresses stripped out. Request and response bodies are not stored. Read the log at Credit System → API & Webhooks or at GET /wp-json/pcs/v1/webhooks/{id}/deliveries.

URL rules

A webhook URL must use the http or https scheme and must pass wp_http_validate_url(), which rejects loopback addresses, RFC 1918 ranges such as 10.0.0.0/8 and 192.168.0.0/16, and link-local addresses such as 169.254.169.254. That check runs at save time and again at delivery time. A duplicate URL on a second active webhook is rejected, and test pings are rate-limited to 10 per 5 minutes per admin user.

What this doesn’t do

  • No ordering guarantee across events. Each delivery is an independent background job, and a retried event arrives after events triggered later.
  • No server-side replay. The delivery log records outcomes only, so an unaccepted payload cannot be re-sent.
  • No delivery to private or loopback addresses, which rules out testing against localhost. Use a public tunnel endpoint during development.
  • No badge.earned event unless the Rewards gamification toggle is on.
  • No filtering beyond the event list. A subscriber to credit.added receives every addition, and the type field distinguishes a purchase from a refund.