paymentRequest object as
TRANSACTION_INITIATED, plus a reason
saying what changed and what the values were before.
One event name for every kind of change. Read reason to learn which. Add a
case for a new reason rather than subscribing to a new event each time there
is another way for a transaction to move.
All deliveries share the common envelope
(eventId, eventName, timestamp, customerId).
Reasons
A status change outranks an amount change. When a single update both moves
the status and changes the amount, you receive the status reason —
COMPLETED,
CANCELLED, EXPIRED or FAILED — and never AMOUNT_*. The new total is in
paymentRequest.amount either way, so nothing is lost.Amounts are always the new total
paymentRequest.amount is what the transaction is worth now — never a
delta, in every case including AMOUNT_INCREASED and AMOUNT_REDUCED.
previousAmount is what it was, so the change is amount - previousAmount if
you need it.
Coerce before you compare.
previousAmount is always a JSON number.
amount is one in practice too, but it is passed through exactly as the
transaction stores it rather than normalised by us — the same as on
TRANSACTION_INITIATED. So take the
difference with Number(amount) - Number(previousAmount) rather than assuming
either type.billing_purchase_total carries the total and amount carries the individual
leg.
Fields
Beyond thepaymentRequest object documented on
TRANSACTION_INITIATED:
Payment request statuses
Any of these can appear aspaymentRequest.status or previousStatus, and all
six are returned by the payment requests API.
A move into or out of
PENDING_TM or PENDING_CANCELLATION does not send an
event on its own. You see those statuses on an event only when the same update
also changed the amount, which reports AMOUNT_INCREASED or AMOUNT_REDUCED
with the monitoring status attached.Examples
One perreason, for the same £57 fuel purchase. Keys arrive sorted — see
verifying the signature.
- AMOUNT_INCREASED
- AMOUNT_REDUCED
- COMPLETED
- CANCELLED
- EXPIRED
- FAILED
2xx within 5 seconds. The body is ignored.
Test vector
Secrettest_secret_do_not_use and the AMOUNT_INCREASED body above, sent
exactly as this single line with no whitespace:
Orenda-Payload-Signature
4d0f3a833dc7934809027234cdb679ed611dc8dae0f1b0dc515f731f703b6e0e.
What does not send one
Deliberately silent, because none of these is a change to the transaction:- Internal bookkeeping. Monitoring states, and identifiers stamped on the row after the fact — see the statuses above.
- A cancellation you have only requested. It can still be reverted, so
CANCELLEDis sent when the cancellation is confirmed, not when it is asked for. - A retry of a failed payment. Re-posting with an idempotency key whose
first attempt failed reopens the same request; the
FAILEDtoPENDINGmove is not announced. - A re-write of the row that does not change it.
- An unconfirmed card hold released after 7 days. No payment request was
ever created, so there is nothing to update.
EXPIREDis a different case — an authorisation that was confirmed and then completed at zero.
AMOUNT_INCREASED
events and then a COMPLETED, so size for more of these than
TRANSACTION_INITIATED.
Not only cards
This fires for every transaction type you already receiveTRANSACTION_INITIATED for, not just card purchases. Bank transfers, direct
debits and batch items change status but not amount, so they send COMPLETED,
FAILED and CANCELLED only.
Duplicates and ordering
Each change is attempted once and never retried, but a duplicate is still
possible — see delivery behaviour. Dedupe on
eventId, which is distinct per change and is repeated rather than reissued
if the same change is delivered again.Do not build a key out of reason and the amounts. A purchase that goes
50 → 60 → 50 → 60 sends two AMOUNT_INCREASED events identical in every field
but eventId and timestamp, and the second one is a real increase you need.Arrival order is not delivery order. Events for one account are normally
delivered in order, but a redelivery can place an earlier change after a later
one. Order on the payload rather than on arrival:
previousStatus and
previousAmount say what state the change started from.So an event whose previousAmount does not match the total you are holding is
one of three things — one you have already applied, one that arrived early, or
one whose predecessor you never received. We attempt delivery; we do not
guarantee it. Treat a mismatch as “my state is stale” rather than as a sequence
you can always reassemble, and re-read the payment request, which is
authoritative.paymentRequest.amount is the total as of this event, not necessarily the
current one: a later change may already have happened by the time you process
it.COMPLETED and TRANSACTION_CREATED race.
A settled card purchase produces both, from two independent processors, so
either can arrive first. Do not wait for one before handling the other, and do
not treat their order as meaningful.