Out-of-band authentication
Out-of-band means the cardholder proves who they are somewhere other than the checkout page. Instead of reading a code out of an SMS and typing it into a merchant's form, they get a prompt in an app they are already signed in to and approve with a biometric.
Before you begin
- 3-D Secure enabled on your program, and out-of-band enabled on top of it.
- A push channel to your own application that you trust to deliver in seconds.
- The
authentications:readandauthentications:writescopes on your credential. Neither is granted by default. - A step-up your app can perform on the device: biometric, or the device passcode.
How it works
The ACS decides to challenge and picks out-of-band. From there the cardholder stays in your app, and the merchant's checkout waits on the answer the same way it would wait for a passcode.
Two things about that sequence shape the implementation.
The authorization is separate. Approving authentication does not approve a payment. The authorization follows, and it can still decline for insufficient funds or a frozen card. Your app should say "confirmed", not "paid".
The authentication object
{
"id": "authn_01HX8ZQ2K4M7P9RSTVWXYZABCD",
"object": "authentication",
"status": "pending",
"method": "out_of_band",
"card_id": "card_01HX8ZQ2K4M7P9RSTVWXYZABCD",
"cardholder_id": "chd_01HX8ZQ2K4M7P9RSTVWXYZABCD",
"merchant": { "name": "Kopi Roastery" },
"amount": { "amount": 4250, "currency": "SGD" },
"created_at": "2026-09-27T09:38:00Z",
"updated_at": "2026-09-27T09:38:00Z"
}
status | Meaning |
|---|---|
pending | Waiting for an answer. The merchant's checkout is still open |
approved | You approved it. The authorization follows |
declined | You declined it, or the cardholder did |
timed_out | Nobody answered in time. The purchase fails at the merchant |
method is out_of_band on every object you will see. A passcode challenge
produces no authentication object at all, because the ACS runs it end to end and
your app is not involved.
Steps
1. Subscribe to authentication.challenged
The event carries the whole object, so your handler has what it needs to raise a prompt without a second call.
authentication.challenged a challenge is waiting; the window is minutes
authentication.approved answered yes
authentication.declined answered no, by the cardholder or by your step-up
Treat authentication.challenged as the highest-priority event your endpoint
handles. It is the only one on this platform where a slow acknowledgement costs
the cardholder a purchase.
2. Push to the device, then step up
Show the cardholder what they are approving, from the object: the merchant name and the amount. Do not paraphrase the amount or round it.
Then step up before you send the answer. Biometric or device passcode, immediately before, not the session they already have open. An app that approves whenever it happens to be unlocked provides no authentication at all.
3. Answer
curl -X POST "$ALETA_BASE_URL/v1/authentications/authn_01HX8ZQ2K4M7P9RSTVWXYZABCD/approve" \
-H "Authorization: Bearer $ALETA_ACCESS_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
POST .../decline takes the
same shape. Both return the updated object.
Answer once. The call is idempotent on the Idempotency-Key, and a second answer
with a different key returns 422 authentication_not_pending.
4. Handle the ones nobody answers
If your push channel is down,
GET /v1/authentications?status=pending
lists what is waiting, and
GET /v1/authentications/{id}
reads one back. Use them when your notification path breaks. They do not work as
a polling loop: at the rate a challenge expires, polling often enough to be
useful would cost you your rate limit.
5. Fall back to the passcode path
The ACS picks the method. A cardholder without your app installed, or signed out, or on a device you cannot reach, gets a passcode instead, and your product has to work when that happens. See how a challenge works.
Verifying
authentication.challengedreaches your endpoint within seconds of the cardholder submitting a purchase.- Your prompt shows the merchant and amount from the object, unrounded.
- Approving returns the object with
status: approved, and the authorization arrives separately. - A second approval on the same authentication returns
authentication_not_pending. - Letting one expire clears your prompt on your own timeout, with no event and
no
expires_atto wait for. - A cardholder without your app still completes a purchase by passcode.
Common errors
| Status | code | Fix |
|---|---|---|
403 | insufficient_scope | The credential lacks authentications:write |
404 | authentication_not_found | Wrong id, or it belongs to another organization |
422 | authentication_not_pending | Already answered, or expired. Read the object for which |
422 | operation_unsupported | Out-of-band is not enabled on this program |
Related
- How a challenge works — the passcode path, which works today
- 3-D Secure — why authentication exists and what you control
- Webhooks — delivery and ordering guarantees