Documentation
Integrate AnybanQID authentication and Verifia verification, bills and document signing into your application.
Quickstart
AnybanQID is an OIDC provider. The citizen approves on their phone with a biometric-gated device key and you receive standard OAuth tokens. Today you start a login either from your server with CIBA (you know the citizen's NIC) or by creating an order and showing it as a QR code or a same-device app link (see App approval). The browser redirect flow (Authorization Code + PKCE) is coming soon. The issuer is https://id.anybanq.lk/oidc — note the path, which every endpoint sits under — and all endpoints are discoverable at the well-known configuration URL.
1 · Discover the endpoints
curl https://id.anybanq.lk/oidc/.well-known/openid-configuration2 · Configure your OIDC client
// AnybanQID is standard OpenID Connect — use any certified OIDC client,
// e.g. openid-client:
import { Issuer } from 'openid-client';
// Discover from the issuer URL itself: a certified client checks that the
// issuer in the document matches what it asked for.
const issuer = await Issuer.discover('https://id.anybanq.lk/oidc');
const client = new issuer.Client({
client_id: process.env.ANYBANQID_CLIENT_ID!,
client_secret: process.env.ANYBANQID_CLIENT_SECRET!,
redirect_uris: ['https://app.example.lk/auth/callback'],
response_types: ['code'],
});AnybanQID — use cases
AnybanQID is a digital identity service: a citizen proves who they are once (bank-account proofing), then approves logins, transactions and document signatures on their phone with a biometric-gated device key.
Citizen (app user)
Enroll
Register with NIC → prove a bank account (JustPay OTP) → bind this device.
Approve a login
Scan the QR or open the app → review who is asking and why → approve with Face ID / fingerprint.
Sign a document
Review the document hash and sign with the device key (PAdES).
Pay bills
See every bill addressed to their NIC and pay via the Verifia pay-link.
Relying party (your app)
Login with AnybanQID
Show a QR code or same-device app link (App approval), or use CIBA. Browser redirect coming soon.
Decoupled login
CIBA: authenticate a known NIC with no browser, e.g. call-centre or POS.
Transaction approval
Create an order with signed display data (amount, merchant).
Request e-signature
Send a signing request; the citizen signs on-device.
Authorization Code + PKCE
Not available yet. The browser redirect flow is documented here so you can plan for it, but it cannot be completed today — integrate with CIBA or App approval (QR / same-device link) instead. When it launches, all AnybanQID clients — public and confidential — must use PKCE with the S256 method.
// 1. Redirect the user to authorize
const { url, verifier } = AnybanQID.buildAuthorizationUrl({
scope: 'openid profile',
acrValues: 'loa2',
});
res.cookie('pkce', verifier, { httpOnly: true });
res.redirect(url);
// 2. Handle the callback
app.get('/auth/callback', async (req, res) => {
const tokens = await AnybanQID.exchangeCode({
code: req.query.code as string,
codeVerifier: req.cookies.pkce,
});
// tokens.accessToken, tokens.idToken, tokens.refreshToken
});App approval (orders)
For flows you drive server-side (e.g. transaction approval), create an auth order and poll its status. The request reaches the citizen's AnybanQID app in one of three ways: they scan the QR code you display, they tap a same-device app link (anybanqid://order/<id>, with ?return= to come back to your site), or it appears when they open the app. AnybanQID does not send push notifications, so tell the citizen to open the app. Display data (amount, merchant) is signed as part of the challenge.
# Create a transaction approval order
curl -X POST https://id.anybanq.lk/api/v1/auth/auth/orders \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "TRANSACTION",
"userRef": { "nic": "<12-digit NIC>" },
"displayData": {
"title": "Confirm payment",
"amount": 24500,
"currency": "LKR",
"merchant": "Keells Super"
},
"acrRequested": "loa2"
}'
# → { "orderId": "...", "expiresAt": "..." } Then poll GET https://id.anybanq.lk/api/v1/auth/auth/orders/<orderId>/statusCIBA — Client Initiated Backchannel Authentication
CIBA lets you authenticate a user without a browser redirect: initiate on the backchannel with a login_hint of nic:<nic>, then poll the token endpoint. AnybanQID advertises poll mode (backchannel_token_delivery_modes_supported: ["poll"]). Enable the CIBA grant on your confidential application first.
# 1. Initiate backchannel authentication
curl -X POST https://id.anybanq.lk/oidc/oauth2/bc-authorize \
-u "$ANYBANQID_CLIENT_ID:$ANYBANQID_CLIENT_SECRET" \
-d 'login_hint=nic:<12-digit NIC>' \
-d 'scope=openid profile' \
-d 'binding_message=Approve login to Demo Bank' \
-d 'requested_acr=loa2'
# → { "auth_req_id": "...", "expires_in": 300, "interval": 5 }
# 2. Poll the token endpoint every {interval} seconds
curl -X POST https://id.anybanq.lk/oidc/oauth2/token \
-u "$ANYBANQID_CLIENT_ID:$ANYBANQID_CLIENT_SECRET" \
-d 'grant_type=urn:openid:params:grant-type:ciba' \
-d "auth_req_id=$AUTH_REQ_ID"
# pending → { "error": "authorization_pending" }; approved → tokensToken refresh & rotation
Refresh tokens rotate on every use with reuse detection — presenting a previously-rotated token revokes the entire family. Always persist the newest refresh token returned.
curl -X POST https://id.anybanq.lk/oidc/oauth2/token \
-u "$ANYBANQID_CLIENT_ID:$ANYBANQID_CLIENT_SECRET" \
-d grant_type=refresh_token \
-d refresh_token=$REFRESH_TOKEN
# → new access token + a NEW refresh token (store it, discard the old one)Scopes & claims
Claims are released from GET /oidc/oauth2/userinfo with the access token, filtered by the scopes granted to your client. Requesting a scope your client is not allowed is not an error — it is silently dropped, so check the scope value on the token response rather than assuming.
| openid | sub | Required for any sign-in. Issues the ID token. |
| profile | name, gender, birthdate | The citizen's name, gender and date of birth. |
| address | address | Postal address. Its own scope — profile does not include it. |
| email, email_verified | Contact email as the citizen entered it. email_verified is always false: AnybanQID does not prove mailbox ownership, so never link or merge accounts by email — use sub. | |
| phone | phone_number | Mobile number in E.164. |
| nic | nic, nic_verified | National identity card number. Restricted — see below. |
| tin | tin | Personal tax number (IRD TIN), self-declared. Restricted. A citizen with none on file is asked for it while approving the login, so the claim is present once they approve. |
| roles | roles | Platform roles. Only issued to AnybanQID first-party clients. |
| offline_access | — | Allows a refresh token. Pair with the refresh_token grant. |
nic is restricted. It releases the citizen's permanent national identifier, so it cannot be enabled from the console: AnybanQID grants it per client, to partners whose regulator requires the NIC on file. Registering a client with nic without that approval returns 403 Restricted scope. Ask your AnybanQID contact.
The citizen sees every item you request, in their own language, on the approval screen before they approve — so ask only for what you use.
Verifia — use cases
Verifia (verifia.anybanq.lk) is the verification & payments platform for businesses: KYC workflows (NIC, document, face, bank, AML…), an OCR bill registry with pay-links, and merchant APIs — all reusing AnybanQID rails where a citizen already holds one.
Merchant / biller
Verify a customer
Compose a workflow (NIC + face + AML…), send a hosted link or embed it.
Issue bills
Register invoices by NIC; each gets an OCR reference and a pay-link.
Send & remind
Deliver by WhatsApp / SMS, track status, remind before due.
Reconcile
Match bill.paid webhooks to invoices by OCR — no manual matching.
Citizen (end customer)
Complete verification
Open the hosted link, capture selfie/ID, confirm OTP — no account needed.
Pay a bill
Open the pay-link, link a bank account once (bank OTP), pay in-window.
Pay from a saved account
Repeat payments confirmed with an AnyBanQ SMS OTP — no re-linking.
Platform staff
Operate tenants
Approve organizations, set pricing, issue bills on a tenant’s behalf.
Monitor & review
Fraud signals, transaction monitoring, manual-review queue for flagged checks.
Verifia — hosted verification workflow
The zero-code path: create a session for your workflow, hand the citizen the link, and receive the outcome by webhook. Camera captures (selfie / ID photo) are uploaded against the session token; each step (NIC, FACE, AML…) runs server-side with real providers.
A flagged face capture (occlusion, glare) or a borderline AML score never auto-passes — it lands in the manual-review queue and the final webhook fires after a reviewer decides.
Verifia — bills & pay-links workflow
Register invoices from your ERP by NIC; Verifia mints an OCR reference (Luhn-checked) and a pay-link, delivers it, and tells you by webhook the moment it is paid — you reconcile purely by OCR.
Verifia — bank account verification workflow
Proves the customer owns a bank account via HNB JustPay tokenization. The bank OTPs only at link time; once an account token exists it is reused — later confirmations use AnyBanQ's own SMS OTP instead of re-registering at the bank. Always tell the customer to have the mobile number registered with their bank.
The bank list you present must come from GET /v1/public/banks — it proxies JustPay's own merchant-bank-list, so institution codes never drift.
Document signing
Send a PDF for legally-recognised e-signature. Verifia is the envelope layer (upload, fields, routing) and AnybanQID is the signature layer — the citizen signs on their phone with a device-bound key, and the certificate that gives the signature legal weight is issued by LankaSign (LankaPay/LPPL, Sri Lanka's licensed CA). A signer with no AnybanQID can fall back to a drawn/typed signature.
Send a document (merchant, in the dashboard → Documents):
- Upload the PDF (unprotected — remove any open/permission password first).
- Add signers in order; each gets a name, an invite channel (email/SMS) and a method (AnybanQID or draw/type). Optionally enter their NIC.
- Place fields — drag Signature, Name, NIC, Date or Text onto the page and assign each to a signer.
- Send for signing — each signer receives their
/sign/<token>link; sequential routing invites the next signer only after the previous one finishes.
Sign (the citizen, from the link):
The required fields must be completed before the sign button enables. On the same device the page deep-links straight into the app's approval screen; on another phone it shows a QR to scan. When the last signer approves, Verifia appends a Certificate of Completion, stamps a public verification code, and emails the finished PDF to everyone.
Verify authenticity — anyone with the code opens /verify/document/<code>: it shows the signers, method and timestamps, and lets a third party drop their copy of the PDF to compare its SHA-256 in the browser (nothing is uploaded). Programmatic check: GET /v1/public/signing/verify/:code.
Get the apps & the CA
AnybanQID app — the citizen installs it once to sign with a device-bound key.
LankaSign (Certification Authority): LankaPay / LPPL — lankapay.net. A citizen's signing certificate is issued to their device the first time they sign a document (or when they join, if their employer requires it); the signature chains to LankaSign's CA.
Error handling
Service errors follow RFC 7807 (application/problem+json) with a code field; OAuth endpoints return standard OAuth error codes. Common codes:
| AnybanQID-AUTH-001 | Challenge expired | Order/challenge TTL elapsed. Create a new order. |
| AnybanQID-AUTH-002 | Signature invalid | Device signature failed verification. |
| AnybanQID-AUTH-003 | Replay detected | Challenge already consumed. |
| authorization_pending | CIBA pending | User has not approved yet — keep polling. |
| invalid_grant | Invalid grant | Code/refresh token expired, reused, or revoked. |
| slow_down | Poll too fast | Increase the CIBA poll interval. |