Waterfall & Fallback
What walks down the candidate list, what stops it, and when a connection is paused.
The waterfall is one pass down the ranked candidate list, inside the POST /payments/{id} call,
while your request is still open. It is not a retry policy and not a background job. When it ends,
the HTTP response is already on its way back to you.
There is no fallback chain to configure. The chain is the candidate list that Orchestration builds, and everything on this page is about how far down it Wajub goes before it gives up.
What moves the ladder on
Only one thing does: the attempt failed in a way that says nothing about the payer.
| What the connection did | What happens next |
|---|---|
| The provider could not be reached | The next candidate is tried |
| The provider reported a temporary problem on its side | The next candidate is tried |
| The provider answered that the operator timed out or is unavailable, that your credentials are refused, or that its float is exhausted | The next candidate is tried, and the connection is flagged when the credentials were refused |
| The connection had no credentials for this channel, so nothing was sent | The next candidate is tried, and the connection is flagged |
Each of these marks the attempt failed, counts against that connection's health on this channel, and moves down one line. The payer is not told anything, because from their side nothing has happened yet.
A request sent and left unanswered is never failed over
When the request reached the provider and no answer came back in time, the provider may have
taken it: a USSD prompt may already be on the payer's phone. The payment stays pending, nothing
else is tried, and the provider's webhook or a verification settles it. A Stripe 5xx is treated
the same way, because Stripe documents it as an undetermined outcome.
What stops it
Besides the unanswered request above, four things, and only one of them is good news.
A provider accepted. That includes accepting as pending and as requires_action. The
provider owns the payment from that moment and nothing else is tried.
A provider refused for a reason that would repeat. An empty wallet, an unregistered number, an
operator ceiling, a declined card, a prompt the payer let expire. Wajub answers 402 on the spot
and does not touch the next candidate, because asking a second provider would ask the same person
the same question. A card declined at Stripe is final, and error_code carries Stripe's own
decline code.
A connection of yours is misconfigured in a way the provider does not call a refusal of your credentials. A required field absent for that channel, for example. That stops the ladder rather than skipping to the next connection, so a single broken connection at the top of your list can cost you every payment routed through it. Credentials are the exception: missing ones send nothing and rejected ones are a path failure, so in both cases the next connection is tried, and the connection is flagged, as described below.
Three providers have been tried. Wajub tries at most three providers per
POST /payments/{id} call, on every account. A candidate list ten deep is still walked three lines
at most, and the request ends with 402 even though eligible connections were left untried. A
connection passed over for missing credentials counts as one of the three.
How long the call can take
A provider that does not answer in time is the
unanswered request above: the payment stays pending and nothing else
is tried. With three providers tried, POST /payments/{id} can stay open close to a minute. Give
your own HTTP client at least 90 seconds on that call, so that it does not give up on a payment
Wajub is still placing.
A refusal is not the end of the payment
A 402 leaves the transaction at pending, not failed. The payer can try again on the same
payment with another number or another card, and that second call routes from scratch. Whether
you offer them that is your decision, in your own code.
A card stays with its provider
A card the payer typed into a provider's own fields can only be charged by that provider: a Stripe.js payment method, a saved Paystack authorization. Another provider cannot read it, and the first one may already have debited the card.
So that connection is the only one tried, whatever the ranking put first, and a failure is never
failed over to another connection. If that connection cannot be tried at all, because it is paused
on this channel or no longer active, the payment answers the same 422 as an
empty list rather than going to a provider that cannot
charge the card.
A step the provider opened stays with it
Two kinds of call are made to the provider that opened them, and never fail over:
- A provider's own payment window (
client_session, the provider's hosted page or SDK sheet the payer completes): it is opened on the first provider of the ranking, and if that provider cannot open it, the call fails rather than trying the next connection. - A confirmation (a code, a PIN, a phone number, a birth date or an address the payer sends after the charge started): it goes to the provider that asked for it, which holds the pending charge. Another provider knows nothing of that charge.
A connection the provider refuses
When a connection has no credentials for the channel, or the provider rejects the key it holds, Wajub records it on the connection.
| Where | What you see |
|---|---|
| Orchestration, Providers, and the Matrix | A Connection refused badge on the connection |
| The provider page | An alert with the error code and when it was last seen, and Fix the connection |
| Your team's email | One email within about five minutes of the flag, when email notifications are on, and another only if the connection is flagged again after the flag cleared |
The flag clears when you save different credentials, when a connection test passes, or on the next payment the connection takes. A payment that only found connections without credentials is not counted as a decline against the payer: the configuration was at fault, not them.
Every attempt leaves a row
Each attempt is listed on the payment with the provider that was tried, its response, the latency and its rank in the decision. A payment that cascaded twice shows three of them, in order.
You read them in the Dashboard on the payment itself, under the timeline, and in Konsole next to the decision that produced them. They are the only place the attempt history exists: the payment object returned by the API carries none of it.
One payment, two providers
- 12:31:40
attempt 1status: failedRank 1 in the decision, placed there by a routing rule. The provider answered that the operator had timed out, so the failure counted against this connection on this channel and the next connection was tried.
- provider
- cinetpay
- response
- timeout
- latency
- 8.0 s
- 12:31:49
attempt 2status: processingRank 2, the first connection no rule named. The operator agreed to prompt the payer, which is an acceptance, and the ladder ended here.
- provider
- flutterwave
- response
- pending
- latency
- 0.9 s
The second row is where most of the confusion about the cascade comes from.
Nothing fails over once the push is accepted
A mobile money provider answers pending the moment the operator agrees to send the prompt,
seconds before the payer has touched their phone. That is an acceptance and it ends the ladder.
If the payer never confirms, the payment fails minutes later through verification or a provider
webhook, long after your call returned, and no other provider is tried. Most of what merchants
read as a failed cascade is this: the cascade never ran, because the first provider said yes.
Which failures count against a connection
Failures that come from the payer never count against a connection: an empty wallet, a wrong or unregistered number, an operator limit, a card declined by its bank or by the provider's fraud screen, a prompt the payer did not confirm. Another provider would get the same answer, and counting them would slowly push your traffic away from a perfectly healthy connection.
Failures on the provider's or the operator's side do count. They lower the connection's recent success rate on that channel, and a connection that keeps failing is paused, as described below.
The circuit breaker
When a connection keeps failing on one channel, Wajub pauses it on that channel for a few minutes,
before it can cost you more payments, and brings it back automatically once it works again. Only
your account and that channel are affected: a connection going bad on cm.mtn keeps serving
cm.orange, and every other merchant is untouched. Payments and payouts share a connection's
health on a channel.
A paused connection is not tried, which is why a connection you expected to be tried can be absent from the ranking entirely. It is not left out silently: Konsole shows it as Skipped (circuit open), and the Matrix shows the circuit on that connection's cell.
Routing decision · cm.mtn · 25 000 XAF
One connection paused, two ranked, the first one carried it.
- 1

flutterwaveaccepted0.9 spriority 100 - 2

pawapaynever calledpriority 100 cinetpaySkipped (circuit open). The connection was paused on this channel after repeated failures.
If that connection was your preferred one, the effect is visible and confusing without this page: a rule pointing at CinetPay looks ignored for a few minutes. It was not ignored, the connection was paused and could not be placed. If the rule names a fallback provider, that one takes CinetPay's place for as long as the pause lasts.
Two providers can never both charge
Within one call, Wajub stops at the first acceptance, so at most one attempt succeeds. The part that needs care is the call itself being repeated.
Send an Idempotency-Key header on POST /payments/{id} and a repeat of the same call within 24
hours returns the same response without running a second waterfall. Without the header there is no
such protection, and a client that retries a request whose response it never saw can open a second
cascade on a payment that already succeeded.
One key per payment attempt
Derive the key from something stable in your own system, such as the order reference plus the
attempt number. The key is validated as up to 128 characters of A-Z a-z 0-9 . _ : -, and
reusing it with a different channel, data or split within 24 hours answers 422. See
Idempotency.
When the list was empty to begin with
A 422 on the channel key means no candidate survived the filters, so nothing was tried and
nothing can be retried elsewhere. It is an account configuration problem, not a payer problem: the
channel, the currency, a provider Wajub cannot charge through yet, or connections paused on this
channel left nothing to try. A card held by one provider whose connection cannot be tried ends here too.
The fix is never in your request. The routing log shows the connections skipped because they were paused on the channel. For the other reasons, run the same channel, currency and amount through the routing simulator, on a Growth plan or above, which names the reason each connection was left out. A connection you paused yourself, or a provider that is not active, does not appear there at all.
Related pages