Skip to main content
PayChoice sends a POST to your endpoint when a charge is created and again each time its status changes. It is sent for every charge under your merchant account — card or bank, API or Widget — so it is the one place to confirm a payment outcome server-side. It matters most for bank-account charges, which can take up to three business days to settle: the charge is Processing at creation and a second notification carries the final result.

Register an endpoint

1

Expose an HTTPS endpoint

It must accept POST requests and return a 2xx status on receipt. Respond quickly and do the work asynchronously if it’s slow.
2

Register it

3

Store the secret

webhook_secret signs every notification and is only returned by this call. Keep it in a secrets manager; never in source control or logs.
To change the URL later, PATCH /api/v4/merchantwebhooks/{id} with merchant_guid and the new webhook_url. Your secret is unchanged. GET /api/v4/merchantwebhooks/{merchantGUID} lists a merchant’s registrations but requires agency-level credentials. Register separate endpoints for sandbox and live.

The payload

The body is JSON. Fields with a null value are omitted.
A refund notification has the same envelope with a refund object and object_type: "refund".
uuid
The charge ID. Use it to retrieve the charge, or to re-send the notification.
string
Your reference — the reference you sent on the charge, or the Widget’s MerchantReference. Match the notification to your order on this.
string
"0" Approved, "4" Blocked, "5" Dishonoured, "6" Errored, "9" Processing. See Charge status.
string
Decimal with two places.
string
Gateway result and code. See Errors.

Bank charges arrive twice

A bank charge is Processing ("9") when first notified; a later notification with the same id carries the final status. Treat id as the key and the latest notification as current. Make your handler idempotent — receiving the same status twice must be harmless.

Verify the signature

Every notification is signed so you can confirm it came from PayChoice and wasn’t altered in transit. Reject anything that fails.
1

Take the raw body

Compute over the exact bytes of the request body as received — before any parsing or re-serialisation.
2

Compute HMAC-SHA256

Key: the UTF-8 bytes of your webhook_secret string, used as-is. Do not Base64-decode it.
3

Compare to the header

X-Webhook-Signature carries the digest as uppercase hexadecimal byte pairs separated by hyphens, e.g. 3A-F2-9C-…. Strip the hyphens, lowercase, and compare to your own hex digest with a constant-time comparison.
Because each request is individually signed, your endpoint doesn’t need its own authentication scheme. There is no timestamp header, so keep a record of processed id / status pairs if you need replay protection.
IP or domain allow-listing (for example *.paychoice.com.au) and a secret key in the URL can be layered on top for defence in depth, but shouldn’t replace signature verification.

Missed notifications

PayChoice does not retry a notification automatically. If your endpoint was down or returned a non-2xx status, re-send the notifications for the charges you missed:
As a safety net, reconcile by listing charges for any orders still pending on your side after a few minutes.

Next

Reconciliation — looking up charges by your reference.