Fulfil payments from signed events.
A merchant-controlled PBE node can watch an exact PBE-PAY tuple and deliver HMAC-authenticated events to your backend. Webhooks are optional operational data; they do not change consensus and are disabled by default.
Enable on a merchant node
'commerce' => [
'webhooks' => [
'enabled' => true,
'manageToken' => 'random-secret-at-least-32-characters',
'signingSecret' => 'different-random-secret-at-least-32-characters',
'defaultTtlSeconds' => 86400,
'maxTtlSeconds' => 604800,
'allowHttpCallbacks' => false,
'allowPrivateCallbacks' => false,
],
],When enabled, PBE creates the optional webhook storage tables. Nodes with webhooks disabled do not need those tables.
/rpc/commerce/webhooksRegister payment watch
Registers one exact reference + recipient + amount tuple and a callback URL.
Parameters
| Name | Location / Type | Required | Description |
|---|---|---|---|
| Authorization | header | yes | Bearer <manageToken>. |
| recipient | JSON address | yes | Expected merchant address. |
| amount | JSON decimal | one of amount / amountBaseUnits | Expected PBE amount. |
| reference | JSON string | yes | Existing merchant order reference. |
| confirmations | JSON integer | no | Confirmation target. |
| callbackUrl | JSON HTTPS URL | yes | Merchant server callback. |
| ttlSeconds | JSON integer | no | Watch lifetime. |
Request body
{"recipient":"pbe1...","amount":"25","reference":"order_8f42d1","confirmations":12,"callbackUrl":"https://merchant.example/paybyte/webhook","ttlSeconds":86400}cURL
curl -X POST https://your-node.example/rpc/commerce/webhooks -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d @watch.jsonExample response
{"ok":true,"subscription":{"id":"pbewh_...","active":true,"reference":"order_8f42d1","recipient":"pbe1...","amount":"2500000000","requiredConfirmations":12,"lastState":"unpaid"}}/rpc/commerce/webhooks/{id}Inspect payment watch
Reads the current subscription state.
Parameters
| Name | Location / Type | Required | Description |
|---|---|---|---|
| Authorization | header | yes | Bearer <manageToken>. |
cURL
curl https://your-node.example/rpc/commerce/webhooks/pbewh_... -H "Authorization: Bearer $TOKEN"/rpc/commerce/webhooks/{id}/cancelCancel payment watch
Stops a subscription from producing future payment events.
Parameters
| Name | Location / Type | Required | Description |
|---|---|---|---|
| Authorization | header | yes | Bearer <manageToken>. |
cURL
curl -X POST https://your-node.example/rpc/commerce/webhooks/pbewh_.../cancel -H "Authorization: Bearer $TOKEN"Events
| Event | Meaning |
|---|---|
| payment.pending | Matching transaction entered the node mempool. |
| payment.confirming | Canonical transaction detected or its confirmation depth changed. |
| payment.confirmed | Required canonical confirmation depth reached. |
| payment.reorged | A previously canonical/confirmed observation lost confidence, changed transaction, or dropped confirmation depth. |
| payment.expired | Watch expired before confirmation. |
Verify the callback
PBE sends the exact JSON body with these headers:
X-Paybyte-Event: payment.confirmed
X-Paybyte-Event-Id: pbeevt_...
X-Paybyte-Timestamp: 1789168200
X-Paybyte-Signature: v1=<hex-hmac>Compute HMAC-SHA256 over timestamp + "." + exact_raw_body using the node's signingSecret. Enforce a short timestamp tolerance and compare signatures with a constant-time function.
$raw = file_get_contents('php://input');
$timestamp = (int)($_SERVER['HTTP_X_PAYBYTE_TIMESTAMP'] ?? 0);
$provided = preg_replace('/^v1=/i', '', $_SERVER['HTTP_X_PAYBYTE_SIGNATURE'] ?? '');
$expected = hash_hmac('sha256', $timestamp . '.' . $raw, $signingSecret);
if (abs(time() - $timestamp) > 300 || !hash_equals($expected, strtolower($provided))) {
http_response_code(401); exit;
}Idempotent fulfilment
Webhook delivery is at-least-once. Store event IDs and make the merchant order reference unique. In one database transaction: lock the order, verify reference + recipient + amount + canonical confirmation depth, mark paid, grant the product only if not already granted, and commit.
Callback security
By default callbacks require HTTPS. Localhost, private and reserved IPs are rejected, redirects are disabled, and delivery pins the validated DNS result to reduce DNS-rebinding/SSRF risk. Relax these controls only on a node you administer and only for a controlled environment.
For a complete working implementation, download the Golden Sword webhook example.
