Browse wiki · Payment webhooks
Payment webhooks
Confirm payments, fulfill on your server
x402 PayMe stores payment records and integration settings. It does not host products, files, license codes, inventory or buyer access tokens. Keep your catalog and fulfillment in your own system. Send an opaque order reference when creating an invoice; do not put secrets or product contents in descriptions or metadata.
- Create an invoice from your server with a merchant API key and save the invoice ID alongside your own order ID.
- Send the payment link to the buyer.
- After a verified webhook or a server-side status check confirms payment, match the amount, USDC currency and recipient to your order.
- Deliver from your own system, once per paid order. A redirect or submitted transaction is not proof of payment.
Configure a webhook
Open API → Payment webhooks. Configure one public HTTPS endpoint on port 443. Save its signing secret on your server and enable delivery. API clients use GET /webhook, PUT /webhook and POST /webhook/retry with webhooks:read / webhooks:write permissions. Read payment status with payments:read.
Events include payment_intent.succeeded after confirmed workspace payments and invoice.scheduled after an automatic invoice is created. invoice.scheduled is not proof of payment. Platform credit-package purchases are separate.
Verify before fulfillment
- Read the exact raw request bytes, before JSON parsing.
- Read
X-Payment-Timestampand reject timestamps outside your allowed clock skew, for example five minutes. - Compute HMAC-SHA256 using the signing secret over
timestamp + "." + rawBody. Compare the hex digest toX-Payment-Signatureafter itsv1=prefix using a constant-time comparison. - Parse the event, require
payment_intent.succeeded, and verify the tenant matches your configured workspace. - Read
GET /payment-intents/{paymentIntentId}usingpayments:read. Match status, amount, recipient and the invoice ID or opaque order reference against your own order record. Not every event represents an order in your application. - Persist a unique workspace + event ID and queue fulfillment durably. Return a 2xx response after persistence. Ensure your fulfillment itself is idempotent.
Deliveries are at least once; duplicate events are possible if an acknowledgement is lost. Do not treat a browser redirect as proof of payment. X-Payment-Event-ID corresponds to the body's id; event IDs are tenant-local. A new delivery attempt has a new timestamp and signature, but the same event ID.
Retries and operations
Delivery uses bounded workers, an eight-second request timeout and durable retries with backoff up to about 30 minutes. Worker scheduling can add delay. Only 2xx acknowledges a delivery. Redirects are not followed; private, loopback and link-local destinations are blocked during connection, including DNS resolution. The receiver's response body is not retained.
The API tab shows the last 20 deliveries. Failed deliveries can be retried manually or with POST /webhook/retry and {"eventId":123}. Pausing keeps the backlog for later; it is not a deletion. Already-running requests may finish before a pause is saved. Rotating with "rotate":true returns a new secret and pauses delivery; update your receiver, then enable again.
Webhook delivery is included in the invoice credit tariff in this version. No additional credits are charged for retries. Product storage, code delivery and buyer access management are handled entirely by the seller. There is no automatic file hosting, refund, recurring debit or exactly-once execution guarantee.
Delivery diagnostics
The API tab shows the last HTTP status, safe error category, last attempt time, next retry time and request duration. A failed attempt is retried automatically with backoff; use Retry only after fixing the receiver. Pausing the endpoint pauses delivery, not the payment. A timeout may happen after your receiver has processed an event, so deduplicate by event ID.
GET {API_BASE}/webhook includes up to 20 recent deliveries and 20 recent attempt records in recentAttempts. Attempt history expires after 30 days. Response bodies, authorization headers and receiver secrets are not exposed in these diagnostics. A connection error may mean DNS, TLS, a timeout or a blocked destination; it does not mean the payment failed.
Test your receiver
Use Send test event in the API tab, or POST {API_BASE}/webhook/test with webhooks:write. The signed body has type: "webhook.test", test: true and a test_ ID. Acknowledge it without issuing goods or marking an order paid. Tests use the configured public HTTPS endpoint, have no automatic retry, and are limited to one per minute per workspace. They do not create a payment or spend credits.
Download the dependency-free examples: JavaScript client and signature verifier, Python client and signature verifier, Python receiver with a SQLite outbox, and integration instructions. The receiver persists a unique event before acknowledging it. Your fulfillment worker must verify the receipt against its own order and make delivery idempotent. Never put a merchant API key in browser code.