Skip to main content
The event list and the “which inquiry does this mirror” mapping are on Webhook event type list; complete field schemas are in API Reference. The delivery rules — timeouts, accepted responses, retries, URL resolution — are in Webhook Delivery Contract FAQ. This page answers the field-level questions that come up when you are writing the handler and cannot find the value you expected.

What is in the outer envelope?

Exactly two keys:
There is no timestamp, no notification id, no version and no merchant id at the top level, and the signature travels in the X-SIGNATURE header rather than in the body. So dispatch on eventType, and take everything else from result.

Should I dispatch on eventType or on something inside result?

Dispatch on eventType. It tells you which event family you have and therefore the shape of result; the status fields inside result then tell you what happened. Doing it the other way round breaks on the cases where one event type carries several statuses — a payment notification, for example, is the same eventType at the authorization stage and at the final state. The rule of thumb: eventType selects the parser, result’s status selects the business branch.

Which field is my own order number?

Every payload carries both sides of the identity — yours and Waffo’s. Use yours as the join key into your database, and Waffo’s for support conversations. Three notes that save time:
  • paymentRequestId is also the idempotency key you generated for the request, which is why reusing it with a different amount or currency returns A0011. The same applies to refundRequestId and subscriptionRequest.
  • Chargeback names the disputed order originalOrderId, not acquiringOrderId as every other payload does. It is the one payload where the naming breaks pattern, and searching a chargeback by your own order number in the Portal webhook log will not find it — see Webhook Event Dictionary & Batch Replay.
  • A payment or refund payload for a subscription charge also carries a nested subscriptionInfo with subscriptionId, period and subscriptionRequest, which is how you attribute a payment order to a billing period.

Where are the card BIN and the last four digits?

Not at the top level. Card details live inside paymentInfo.payMethodResponse, and the important structural detail is that payMethodResponse is a JSON-encoded string, not a nested object — you must parse it before you can read anything out of it. Once parsed, the card-related keys are: Other keys in the same string cover exchange rate, the customer’s paid amount and currency, and a pay-method reference — see API Reference for the published description. When are they populated? Only for card-type payments — card, and the two mobile wallet products that present a card underneath. For any other payment method, no card keys appear at all. So write your parser to treat every one of them as optional; a missing cardBin is normal, not an error. firstSix, first6, lastFour, last4, maskedCardNumber and cardNo are not field names in any Waffo payload. If you are looking for one of those, you want maskCardData, cardBin or cardLast4.

Can I get userFee from an inquiry or a notification?

No. userFee is not a response field in any merchant-facing inquiry or notification payload. The name does appear in the description of the orderAction data used when you bypass the hosted cashier — but those are keys of that action payload, not fields of an order. For the amounts and fees that are returned on an order, see Fee and tax treatment.

Payout has actFeeAmount — is feeAmount still there? Which do I consume?

Consume actFeeAmount. On the merchant-facing payout payload, feeAmount is not present — actFeeAmount replaced it, and it is the field the published contract carries. Read it together with its two companions: The relationship matters for your bookkeeping: actFeeAmount is the collected amount when feeStatus is DEDUCTED, and 0 when feeStatus is RETURNED or NONE. So do not read a zero fee as “free” without checking feeStatus — a returned fee and a never-charged fee both show zero and mean different things. One naming trap: feeAmount does still exist on the chargeback payload, where it means the chargeback fee. Same field name, unrelated quantity — do not share a deserializer between the two. Payout outcome fields, for completeness: status is IN_PROGRESS, SUCCESS or FAILURE; a reversal is expressed by subStatus of FULL_REVERSED or PARTIAL_REVERSED; a failure carries failureCode and failureReason.

After a customer pays, can I get their bank account so I refund to the same account?

No. No payin notification or order inquiry returns the customer’s bank account number or account holder name. Three fields look like they might be it, and none are: The reassuring part of the answer: for the payment methods where refunding to the original instrument matters, the refund is executed back along the original payment rather than to an account you nominate — so you do not need the account number to get that behaviour. Where a refund does require bank details, the API asks you for them. See Refund funding source and Payment method refund limits.

What does an unpaid, cancelled or timed-out order look like in the payload?

All three arrive as PAYMENT_NOTIFICATION and all three land on the same terminal status — ORDER_CLOSE. There is no separate cancelled, expired or failed status value. The complete set of order statuses you can receive: To distinguish cancel from timeout from decline, read orderFailedReason — also a JSON-encoded string, shaped {"orderFailedCode":"...","orderFailedDescription":"..."}. It is present only when the status is ORDER_CLOSE; a successful or in-flight order omits it entirely, so your parser must tolerate its absence rather than defaulting it. The refund payload’s equivalent is refundFailedReason, and a subscription’s is failedReason, both in the same JSON-string shape. The names failReason, errorCode, errorMsg, channelErrorCode and merchantErrorCode do not exist in these payloads.

Is paymentDetails on a subscription the full history? Does it keep growing?

Yes to both, and this is worth designing for before you have long-lived subscribers. paymentDetails is an array of every period up to the current one, ordered by period ascending, with no cap, no page size and no cursor. A monthly subscription running for years grows the array by one element per period, and the payload grows with it. Each element carries: Two practical consequences:
  • Stream or index it, do not hold it whole. Read the periods you care about — usually the highest period, or the ones you have not recorded yet — rather than diffing the entire array on every notification.
  • paymentDetails[].orderStatus is the period order’s own status, not the merchant-display order status. Besides PAY_SUCCESS and ORDER_CLOSE it can be INIT, TO_PAY or NEED_RETRY while a charge is still pending or retrying. Treat only the first two as final.
A related asymmetry: the same name means two different things in the subscription inquiry contract. On the request paymentDetails is a flag (0 or 1, default 0) asking for the array; on the response it is the array.

My subscription is still ACTIVE but one period’s charge failed. What do I receive?

You receive SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION, and only after that period’s retries are exhausted. Intermediate failed attempts fire no notification at all — the period order goes to NEED_RETRY and waits. What the payload tells you: Set expectations about the reason, because this is a real limitation: the top-level failedReason is the subscription’s failure detail, not the failed period’s, so it does not reliably describe why that period was declined, and the period elements carry no failure code of their own. To get a per-attempt failure reason, subscribe to PAYMENT_NOTIFICATION as well — it fires for every payment attempt and carries that attempt’s orderFailedReason. This is precisely the distinction the subscription notification selection guide makes between the “minimal” and “full” combinations.

If renewal retries run out, is the subscription cancelled?

It depends which period failed, and the two cases behave differently: So do not treat an exhausted renewal as a cancellation, and do not revoke entitlements on a period-changed notification alone. If your product should suspend access after a failed period, that is your policy to implement on the period event — Waffo does not close the subscription for you. How many retries? There is no single number: the retry count and intervals come from your merchant agreement’s renewal retry strategy, resolved per period type — daily, weekly, monthly, quarterly, yearly, or a default. Confirm the figures configured for your account with integration support rather than assuming a number, and see Subscription retry and payment methods.

The customer cancelled inside their wallet app. Which status do I get?

SUBSCRIPTION_STATUS_NOTIFICATION with subscriptionStatus of USER_CANCELLED. The full status set, so you can branch on all of it: Note that PLATFORM_CANCELLED is missing from the published enum list on the subscription status field even though it can be delivered, so handle it as part of the cancellation family — MERCHANT_CANCELLED, USER_CANCELLED, CHANNEL_CANCELLED and PLATFORM_CANCELLED all mean “no longer billing”.

Is a notification body really identical to the inquiry response?

Mostly, but not always — and the exception is the one that will surprise you. Two implications:
  • Do not build your subscription handler by diffing against an inquiry response. The extra array is expected, not a bug, and it is the reason a subscription webhook can be dramatically larger than the inquiry you compared it with.
  • Do not assume key order is stable. Field ordering in a notification body is not guaranteed to match anything, which matters only if you were planning to hash or canonicalise the body yourself. You do not need to: verify the X-SIGNATURE over the raw bytes as received. See Webhook signature verification.

Can I have a complete, real example body for every event type?

Per-event examples are maintained in API Reference rather than here, because each result is the corresponding inquiry response and the API Reference is generated from the live contract — which makes it the only copy that cannot drift. Two cautions when you use it as your fixture source:
  • The published reference is regenerated periodically, so a field that exists in the payload can be missing from the reference for a while. Card keys inside payMethodResponse and subscription add-on details are current examples: they are delivered, and only partly described. Treat the payload as a superset and make your parser tolerant of unknown fields.
  • Do not build fixtures from a document when you can capture the real thing. Run the flow in the sandbox and record what arrives at your endpoint — that is a payload for your merchant configuration, with your payment methods, which is what your handler actually has to survive. See Sandbox Simulation Limits FAQ.