Skip to main content
When a payment reaches a final state, Paywit POSTs a JSON event to your webhook URL. Fulfil orders from payment.paid.

Set the URL

On your app page in the dashboard, under Webhook, save the URL. The response includes a signing secret, whsec_..., shown once. Store it next to your secret key. Use an https URL.

Events

Delivery

Every delivery carries these headers: And this body:
transactionHash is set for payment.paid and null otherwise. clientReferenceId is whatever you passed when creating the payment. Return any 2xx within 10 seconds. Anything else, or a timeout, is retried with growing delays: about 30 seconds, 2 minutes, 10 minutes, 1 hour, 6 hours and 24 hours, six attempts in total. Delivery is at-least-once, so the same event can arrive more than once.

Verify the signature

The signed string is the timestamp, a dot, and the raw request body exactly as received. Compute HMAC-SHA256 with your whsec_ secret, compare it to v1 in constant time, and reject timestamps older than a few minutes.
Read the raw body before any JSON parsing or framework middleware touches it. A re-serialised body will not match the signature.

Handle the event

A safe handler does four things, in this order:
  1. Verify the signature. Reject with 400 if it fails.
  2. Check id against events you have already processed. If seen, return 200 and stop.
  3. Record the id, then act on type. On payment.paid, look up your order by clientReferenceId or paymentId, check that data.amount matches what the order costs, and fulfil it.
  4. Return 200.
If your framework has CSRF protection on POST routes, exclude the webhook route. The request comes from Paywit, not a browser session.

Test it

Point the webhook URL at webhook.site to see real payloads before you write the handler, then switch it to your endpoint. The testing guide walks through a full run.