Skip to main content
Version: 0.0.1

Callbacks and Handling the Customer's Return

Handling the Customer's Return​

Your return page should use this trust order:

  1. Verified callback state
  2. Local server-side payment attempt state
  3. Redirect parameters only as secondary UX context
StateShow
Redirect says success, callback not yet verifiedPayment is being confirmed
Callback verified successPayment successful
Callback verified failedPayment failed
Callback validation failedSafe 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 validationCode that can be verified server-side

Requirements:

  • Transaction-specific — applies only to the transaction in which callbackUrl was 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 validationCode before trusting the payload

Callback vs Redirect​

RedirectCallback
ChannelBrowserServer-to-server
TimingSynchronous with browserAsynchronous
ReliabilityNot guaranteed to completeMore reliable
PurposeCustomer-facing UXAuthoritative confirmation
VerifiedNoYes — via validationCode

For merchant-wide, event-style notifications across broader activity (not transaction-specific), see Webhooks instead.


Callback Payload Reference​

FieldTypeNotes
paymentReferencestringUnique payment reference assigned by Zenith
customerNamestringName of the customer
customerReferencestringReference provided by the merchant
paymentStatusnumberNumeric status code of the payment
paymentStatusStringstringHuman-readable payment status
baseAmountnumberBase transaction amount before fees
fundsToMerchantnumberNet amount payable to the merchant
accountOrCardNostringMasked account or card number
paymentAccountstringPayment account type such as Card or Bank
processingDatestringUTC ISO-8601 datetime
settlementDatestringUTC ISO-8601 datetime
processorReferencestringReference from the processor or gateway
isPaymentSettledToMerchantbooleanWhether the payment has been settled to the merchant
failureCodestringFailure code if unsuccessful, otherwise empty
failureReasonstringFailure reason if unsuccessful, otherwise empty
paymentCardstringCard brand such as MasterCard or Visa
merchantUniquePaymentIdstringUnique ID supplied by the merchant for the payment
merchantCodestringMerchant identifier code
transactionSourcenumberNumeric identifier for the source channel
transactionSourceStringstringHuman-readable source channel
customerFeenumberFee charged to the customer
processedAmountnumberTotal processed amount including fees
cardCategorystringCategory of card such as Domestic or International
cardInformationSavedbooleanWhether card information was stored for tokenisation
validationCodestringSHA3-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 transaction reference (validation code)
  • reference depends on mode — for Mode 1 (Tokenise), this is the Token output parameter

Reference field and amount format by mode​

ModeReference fieldAmount in hash
0 — Make PaymentpaymentReferencewhole cents
1 — Tokenisetokenwhole cents
2 — Custom PaymentpaymentReference"0" (literal zero)
3 — PreauthorisationpreauthReferencewhole cents

Mode 3 note: Preauthorisation callbacks carry preauthStatus and preauthReference in place of paymentStatus and paymentReference.

Steps to validate:

  1. Receive the callback payload
  2. Extract merchantUniquePaymentId, the returned reference, and validationCode
  3. Rebuild the input string in the exact order above
  4. Generate SHA3-512 server-side
  5. 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 validationCode before 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 merchantUniquePaymentId to prevent double-processing
  • Show a pending / confirming state if redirect returns before callback validation completes