Skip to main content

Webhooks

Webhooks deliver payment, order, bridge, payout, and seller dispute updates from Peer Pay to your server. Each delivery is a signed POST with a JSON body. The event family determines the snapshot in data; see events and payloads.

Recommended flow:

  1. Create a webhook and store the signing secret returned once on creation.
  2. Subscribe to the events your fulfillment logic needs.
  3. Verify the signature and timestamp on every delivery. See verification and handler checklist.
  4. Store X-Webhook-Id and skip deliveries you have already processed.
  5. For order-family events, reconcile your merchant order record from data.order.id and data.payment.id. Do not cancel an order solely because PAYMENT_EXPIRED arrived, and keep chargeback state in its own field.

Create a webhook​

curl -X POST https://api.pay.peer.xyz/api/v1/webhooks \
-H "Content-Type: application/json" \
-H "X-API-Key: your_api_key" \
-d '{
"url": "https://yoursite.com/webhooks/zkp2p",
"events": ["PAYMENT_SETTLED", "PAYMENT_FAILED", "PAYMENT_EXPIRED", "ORDER_FULFILLED"]
}'

The response is 201 and carries the signing secret once:

{
"success": true,
"message": "Webhook created",
"responseObject": {
"webhook": { "id": "...", "url": "...", "events": ["..."], "active": true, "...": "..." },
"secret": "whsec_..."
},
"statusCode": 201
}

Store responseObject.secret immediately. It is never returned again; list and update responses omit it.

Request fields:

FieldRequiredDescription
urlYesHTTPS endpoint that resolves to a public address
eventsYesOne or more event names. The full list is on events and payloads
customHeadersNoUp to 10 { "key", "value" } pairs sent on every delivery, for example a shared bearer token
Destination rules
  • Plain HTTP, private, and loopback destinations are rejected with 400.
  • URLs with embedded credentials, such as https://user:pass@example.com/hook, are rejected.
  • The hostnames localhost and metadata.google.internal, and any hostname ending in .internal, .local, or .localhost, are rejected whatever they resolve to.
  • Every delivery re-resolves and re-validates the host. A destination that stops resolving to a public address fails that delivery immediately, with no retries.
Custom header rules
  • Keys are trimmed, lower-cased, and must be valid HTTP header tokens.
  • Values cannot contain CR, LF, or NUL.
  • Duplicate keys are rejected.
  • Reserved keys are rejected: x-webhook-id, x-webhook-timestamp, x-webhook-signature, host, content-type, content-length, connection, expect, forwarded, proxy-authenticate, proxy-authorization, te, trailer, transfer-encoding, upgrade, and any key starting with proxy- or x-forwarded-.

Webhooks are registered per environment. A registration made with the sandbox key only receives sandbox deliveries, and one made with the live key only receives live deliveries, each with its own signing secret.

Subscriptions​

Add DISPUTE_OPENED, DISPUTE_PAID and DISPUTE_ESCALATED to learn when a seller dispute on your order asks you to pay the seller.

Subscribe to ORDER_FULFILLED, PAYMENT_SETTLED, PAYMENT_FAILED, PAYMENT_EXPIRED, ORDER_CANCELLED, and ORDER_RESIZED. Add PAYMENT_CHARGEBACKED, ORDER_PARTIALLY_CHARGEBACKED, and ORDER_CHARGEBACKED if you accept Venmo or PayPal; Cash App is non-chargebackable. Add the bridge events if your payout flow crosses chains.

If you create open-amount orders, add ORDER_AMOUNT_SET to learn the amount the customer chose before they pay. Existing webhooks do not receive it until you add it.

ORDER_FULFILLED is the release signal. The others tell you why an order is not progressing, or that its amount moved. The full event list, what each one carries, and the payload shapes are on events and payloads.

Endpoint requirements​

  1. Accept POST with a JSON body.
  2. Verify the signature before acting on the payload. See verification and handler checklist.
  3. Return 2xx within 30 seconds.
  4. Process asynchronously.

Retry policy​

A delivery is attempted up to 7 times. Each attempt waits 30 seconds for a response; a non-2xx status, a timeout, or a connection error schedules the next attempt with growing delays:

After attemptNext attempt in
11 minute
25 minutes
330 minutes
42 hours
58 hours
624 hours

After the seventh failure the delivery is marked FAILED and not retried. Redirects count as failures, so register the final URL. Every attempt carries the same X-Webhook-Id and a fresh timestamp and signature.

Delivery statuses​

StatusMeaning
PENDINGAwaiting delivery or retry
DELIVEREDDelivered with a 2xx response
FAILEDAll 7 attempts exhausted, or delivery aborted because the webhook was inactive, its URL no longer resolves to a public HTTPS host, or it was a test delivery whose single attempt failed

These are also listed under webhook delivery status.

Manage webhooks​

Authenticate with X-API-Key, or with a dashboard session token in Authorization: Bearer, but never both; sending both is rejected. With a dashboard session, creating, updating, deleting, and testing webhooks needs the Owner or Manager role.

List​

curl https://api.pay.peer.xyz/api/v1/webhooks \
-H "X-API-Key: your_api_key"

Update​

curl -X PATCH https://api.pay.peer.xyz/api/v1/webhooks/{webhookId} \
-H "Content-Type: application/json" \
-H "X-API-Key: your_api_key" \
-d '{"active": true}'

PATCH accepts any of url, active, events, and customHeaders.

Delete​

curl -X DELETE https://api.pay.peer.xyz/api/v1/webhooks/{webhookId} \
-H "X-API-Key: your_api_key"

Test​

curl -X POST https://api.pay.peer.xyz/api/v1/webhooks/{webhookId}/test \
-H "X-API-Key: your_api_key"

Sends one signed, synthetic ORDER_CREATED delivery with data.test set to true and every snapshot null, so your handler can be exercised before a real order exists. The delivery is not retried. responseObject carries success, deliveryId, responseCode, and error. Limited to 5 calls per 10 minutes per merchant.

Deliveries​

curl https://api.pay.peer.xyz/api/v1/webhooks/{webhookId}/deliveries \
-H "X-API-Key: your_api_key"

Returns the 50 most recent deliveries, newest first, as { deliveries, hasMore }. Each delivery carries payload, status, attempts, lastAttemptAt, nextRetryAt, and responseCode.