Skip to content
Preptima
hardDesignScenarioMidSeniorStaff

A third party initiates a payment from your customer's account and the money does not arrive. Design the flow so it is clear what the third party was told and who answers to the customer.

A payment initiation service instructs but never holds the money or the credentials, so the bank authenticates its own customer and owns the outcome. The design work is a status model with honest terminal states and a redress path that makes the customer whole first and apportions fault afterwards.

6 min readUpdated 2026-07-29Target archetype: Enterprise Captive, Product Startup
Practice answering out loud

What the interviewer is scoring

  • Does the candidate keep authentication of the customer with the bank rather than delegating it to the third party
  • Whether the status model distinguishes accepted, settled and rejected, and marks which states are terminal
  • That the same instruction cannot be executed twice when the third party retries an ambiguous call
  • Whether the customer is made whole before the parties apportion fault between themselves
  • Does the answer treat third-party traffic as regulated capacity rather than as load to shed first

Answer

What the third party can and cannot hold

Start by being precise about the roles, because the liability question at the end of the scenario is decided by them. A payment initiation service provider instructs a payment from an account it does not hold, at a bank where it is not the customer, on the authority of a person who is the bank's customer and not its own. It never touches the funds. Under the European framework it must also never hold the customer's banking credentials, which is the whole reason the redirect and decoupled patterns exist rather than the older practice of handing over a username and password.

That leaves a clean division. The third party owns the user's journey, the payee details and the instruction. You own the customer relationship, the authentication, the balance check, the execution on the rail and the truthful reporting of what happened. You cannot outsource any of the second list, and every design mistake in this area comes from blurring the boundary in one direction or the other.

The authentication is yours

Strong customer authentication is performed by the bank, because it is the bank's customer being authenticated and the bank's credentials being used. That has two consequences worth stating.

The first is dynamic linking. The authentication code the customer generates has to be tied to the specific amount and the specific payee, so that a code obtained for one payment cannot execute another. The reason is exactly the scenario in the question: if the code is generic, then a discrepancy between what the customer approved in the third party's interface and what you executed is undetectable after the fact, and you have no way to say whose version of the instruction was authorised. With dynamic linking, the thing the customer approved and the thing you executed are provably the same object or the payment fails.

The second is that you must not make your own channel the privileged one. Whether the customer authenticates by being redirected to your app, by being pushed a request in your app while remaining in the third party's interface, or by another supported pattern, the authentication has to be available and usable through the third party's flow. A journey that technically works but adds screens, warnings and re-consents that your own app does not impose is an obstacle, and supervisors have treated friction of that kind as a compliance problem rather than a design preference.

sequenceDiagram
  participant U as Customer
  participant T as Third party
  participant B as Your bank API
  participant R as Payment rail
  T->>B: Create payment consent with amount and payee
  B->>U: Authenticate and confirm the instruction
  U-->>B: Approval linked to that amount and payee
  T->>B: Submit payment against the consent
  B->>R: Execute
  B-->>T: Status now and status later

The interesting part is the last line: there are two answers to "what happened", one available immediately and one only after the rail has settled, and conflating them is the defect this question is built around.

The status model is the contract

Almost all of the pain in the scenario comes from a status vocabulary that promises more than it knows. Design it as an explicit state machine with three properties.

Separate the states that mean "we have accepted the instruction and validated it" from those that mean "the money has left" and from those that mean "this will never happen". The ISO 20022 status vocabulary that open banking specifications draw on already makes this distinction, and the important discipline is to use the accepted states honestly: for an instruction on an asynchronous rail, accepted is all you know at the moment you respond, and returning anything that reads as completion is a lie that the third party will show to a customer.

Mark which states are terminal. A third party polling a payment needs to know when to stop, and more importantly when it may safely tell its user the payment failed. A rejection that might later become a success is the worst possible response, because it invites the user to pay again by another route while the first payment is still alive on the rail.

Make the transitions available rather than merely eventual. Whether the third party polls a status endpoint or you notify it, the requirement is that a payment which settles hours later becomes visible without the third party having to guess. Returning a final answer only in a reconciliation file the following day is what turns a normal asynchronous payment into a support case.

One instruction, however many calls

The ambiguity in the question — the money did not arrive, so what did the third party do next? — usually resolves into a retry. A response was lost, so the third party re-submitted, and now there is a risk of two payments where the customer authorised one.

The consent or payment resource created before authentication is the natural idempotency anchor. Submission is an operation on that resource, so a second submission against an already-submitted resource returns the existing payment and its current status rather than creating another. A repeated create call carrying the same third-party instruction identifier returns the original resource rather than a new one. Both rules have to hold across your own retries and failovers, which means the identifier is persisted with the payment before you call the rail, not after.

The customer must not have to work out which of you failed

This is where a good answer separates itself. A customer who authorised a payment that did not arrive is not in a position to determine whether the third party's instruction was wrong, your execution failed, or the beneficiary bank rejected the credit, and the framework does not ask them to. The obligation sits with the account-holding bank as the customer's counterparty: where a payment was unauthorised or incorrectly executed, you put the customer back in the position they should have been in, and then recover from the third party where the third party was responsible. Fault is settled between institutions, afterwards, on evidence.

That principle drives a concrete engineering requirement. You need to be able to reconstruct, for a single payment, the identity of the third party that called you and the certificate it presented, the instruction as received, the authentication event and what it was bound to, the message sent to the rail and the response, and every status you returned with its timestamp. Without that record the apportionment cannot happen and you absorb every dispute. With it, the dispute is a data question.

Do not shed the regulated traffic first

The final trap is an availability one, and it is tempting because the traffic is not yours. When capacity is tight, third-party API calls look like the obvious thing to throttle, and the customers behind them are invisible to you. But the dedicated interface carries obligations about availability and performance comparable to your own channels, with reporting to match, and degrading it selectively is a breach rather than a prioritisation. Treat that traffic as a first-class channel with its own error budget, publish the incidents, and do your rate limiting on genuine abuse patterns such as unattended polling rather than on the class of caller.

The third party owns the journey and you own the truth: authenticate your own customer with the amount and payee bound to the approval, return a status that never claims more than the rail has told you, and make the customer whole before deciding whose fault it was.

Likely follow-ups

  • The third party never receives your response and re-posts the instruction. What guarantees one payment?
  • Your rail settles asynchronously hours later. What status do you return in the meantime, and what must it not imply?
  • The customer says they never authorised it. Walk me through what you refund and what you then pursue.
  • How would you prove to a regulator that your dedicated interface performed as well as your own app?

Related questions

payment-initiationpsd2strong-customer-authenticationpayment-statusliability