Callbacks and Handling the Customer's Return
Handling the Customer's Return
Your return page should use this trust order:
- Verified callback state
- Local server-side payment attempt state
- Redirect parameters only as secondary UX context
| State | Show |
|---|---|
| Redirect says success, callback not yet verified | Payment is being confirmed |
| Callback verified success | Payment successful |
| Callback verified failed | Payment failed |
| Callback validation failed | Safe failure message — do not trust the result |
Do not rely on redirect alone to mark a payment as complete.
Callbacks — What They Are and Why They Matter
When a callbackUrl parameter is included in the plugin payload, Zenith Payments POSTs the transaction result to that URL after payment is processed.
Callbacks are the authoritative server-side confirmation path for Hosted Checkout. Redirect is for customer-facing UX; callback validation is what allows your backend to trust the result.
Unlike redirect, a callback:
- Does not depend on the browser completing the journey
- Is not controlled by the user
- Includes a
validationCodethat can be verified server-side
Requirements:
- Transaction-specific — applies only to the transaction in which
callbackUrlwas supplied - Asynchronous — delivery happens in the background after Zenith finishes processing
- Integrator-controlled — you decide the destination via
callbackUrl - Not merchant-wide — does not notify on unrelated transactions (use Webhooks for broader patterns)
- HTTPS POST endpoint — must be a secure endpoint reachable from Zenith Payments
- Backend-owned — receive and validate on your backend, not your frontend
- Validation required — validate
validationCodebefore trusting the payload
Callback vs Redirect
| Redirect | Callback | |
|---|---|---|
| Channel | Browser | Server-to-server |
| Timing | Synchronous with browser | Asynchronous |
| Reliability | Not guaranteed to complete | More reliable |
| Purpose | Customer-facing UX | Authoritative confirmation |
| Verified | No | Yes — via validationCode |
For merchant-wide, event-style notifications across broader activity (not transaction-specific), see Webhooks instead.
Callback Payload Reference
| Field | Type | Notes |
|---|---|---|
paymentReference | string | Unique payment reference assigned by Zenith |
customerName | string | Name of the customer |
customerReference | string | Reference provided by the merchant |
paymentStatus | number | Numeric status code of the payment |
paymentStatusString | string | Human-readable payment status |
baseAmount | number | Base transaction amount before fees |
fundsToMerchant | number | Net amount payable to the merchant |
accountOrCardNo | string | Masked account or card number |
paymentAccount | string | Payment account type such as Card or Bank |
processingDate | string | UTC ISO-8601 datetime |
settlementDate | string | UTC ISO-8601 datetime |
processorReference | string | Reference from the processor or gateway |
isPaymentSettledToMerchant | boolean | Whether the payment has been settled to the merchant |
failureCode | string | Failure code if unsuccessful, otherwise empty |
failureReason | string | Failure reason if unsuccessful, otherwise empty |
paymentCard | string | Card brand such as MasterCard or Visa |
merchantUniquePaymentId | string | Unique ID supplied by the merchant for the payment |
merchantCode | string | Merchant identifier code |
transactionSource | number | Numeric identifier for the source channel |
transactionSourceString | string | Human-readable source channel |
customerFee | number | Fee charged to the customer |
processedAmount | number | Total processed amount including fees |
cardCategory | string | Category of card such as Domestic or International |
cardInformationSaved | boolean | Whether card information was stored for tokenisation |
validationCode | string | SHA3-512 hash used to validate callback integrity |
Example payload structure (illustrative values only):
{
"response": {
"paymentReference": "95408",
"customerName": "Leah Adria",
"customerReference": "a12b1926-811f-411c-b10a-1b330cc50d3b",
"paymentStatus": 3,
"paymentStatusString": "Successful",
"baseAmount": 635.72,
"fundsToMerchant": 635.72,
"accountOrCardNo": "555555XXXXXX4444",
"paymentAccount": "Card",
"processingDate": "2025-08-27T21:48:59.447",
"settlementDate": "2025-08-29T00:00:00",
"processorReference": "ddb3a3436cbc7d77c241",
"isPaymentSettledToMerchant": false,
"failureCode": "",
"failureReason": "",
"paymentCard": "MasterCard",
"merchantUniquePaymentId": "35917c6c-ee6b-4e52-866b-bea51841e796",
"merchantCode": "1337",
"transactionSource": 36,
"transactionSourceString": "Public_Customer_OnlineOneOffPayment",
"customerFee": 63.57,
"processedAmount": 699.29,
"cardCategory": "International Cards",
"cardInformationSaved": false
},
"validationCode": "1dbe5ddcc72b8207e839da42308d0596cbbb72e5bf7074520ec885e3c615bb519848fce4a0c073a9a7e6544854338cab9c0c87bc8c82c54bb3706a91c21a7410"
}
Validating the Callback
The validationCode is a SHA3-512 hash of 7 pipe-separated fields, in exact order:
apiKey|userName|password|mode|paymentAmount|merchantUniquePaymentId|reference
- The first 6 fields are the same values used in the original fingerprint hash
- Only the last field changes — from
timestamp(fingerprint) to the returned transactionreference(validation code) referencedepends on mode — for Mode 1 (Tokenise), this is theTokenoutput parameter
Reference field and amount format by mode
| Mode | Reference field | Amount in hash |
|---|---|---|
| 0 — Make Payment | paymentReference | whole cents |
| 1 — Tokenise | token | whole cents |
| 2 — Custom Payment | paymentReference | "0" (literal zero) |
| 3 — Preauthorisation | preauthReference | whole cents |
Mode 3 note: Preauthorisation callbacks carry
preauthStatusandpreauthReferencein place ofpaymentStatusandpaymentReference.
Steps to validate:
- Receive the callback payload
- Extract
merchantUniquePaymentId, the returned reference, andvalidationCode - Rebuild the input string in the exact order above
- Generate SHA3-512 server-side
- Compare your computed value against the received
validationCode
If they match, the callback is authentic — update payment state. If they don't match, reject the callback, do not trust the result, and log for investigation.
Recovering launch context
To verify a callback you need the original mode, paymentAmount, and merchantUniquePaymentId from the launch — none of which Zenith echoes back reliably across all modes (Tokenise callbacks omit merchantUniquePaymentId).
The stateless approach is a signed callback token: embed a short-lived signed token in the callbackUrl as a query parameter (e.g. ?t=…). The token carries the launch context and is verified by your server before the validationCode check runs. This avoids any session store or database lookup:
callbackUrl: https://your-server/callbacks/zenith?t=<signed-token>
Backend handling checklist
- Accept POST requests over HTTPS
- Validate
validationCodebefore trusting any field - Process callbacks idempotently
- Update local payment state safely
- Log failures and mismatches clearly
Do not trust callback data without validation. Do not rely on redirect as final truth. Do not expose credentials or validation logic to the browser.
Timing and idempotency
Callbacks can arrive before redirect completes, after redirect completes, without redirect completing, or more than once.
- Treat callbacks as idempotent
- Use
merchantUniquePaymentIdto prevent double-processing - Show a pending / confirming state if redirect returns before callback validation completes