AnybanQIDDeveloper Docs

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

bash
curl https://id.anybanq.lk/oidc/.well-known/openid-configuration

2 · Configure your OIDC client

ts
// 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'],
});
PKCE S256 requiredES256 tokensAccess 10m · Refresh 30dacr: loa1 / loa2 / loa3

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

Coming soon

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.

Authorization Code flow
Your AppAnybanQIDPhoneGET /oauth2/authorize (code_challenge)GET /oauth2/authorize (code_challenge)login request in the applogin request in the appapprove (biometric + signature)approve (biometric + signature)302 redirect ?code=302 redirect ?code=POST /oauth2/token (code_verifier)POST /oauth2/token (code_verifier)access + id + refresh tokenaccess + id + refresh token
ts
// 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.

Transaction approval
Your APIAuth SvcPhonePOST /auth/orders (TRANSACTION)POST /auth/orders (TRANSACTION)QR / app link / open appQR / app link / open appGET /orders/:id/status (poll)GET /orders/:id/status (poll)approve + sign displayDataapprove + sign displayDatastatus: APPROVEDstatus: APPROVED
bash
# 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>/status

CIBA — 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.

CIBA poll mode
Your APIAnybanQIDPhonePOST /oauth2/bc-authorize (login_hint)POST /oauth2/bc-authorize (login_hint)auth_req_id, intervalauth_req_id, intervalapproval request in the appapproval request in the appPOST /oauth2/token (grant=ciba) pollPOST /oauth2/token (grant=ciba) pollauthorization_pending…authorization_pending…approve (biometric)approve (biometric)tokenstokens
bash
# 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 → tokens

Token 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.

bash
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.

openidsubRequired for any sign-in. Issues the ID token.
profilename, gender, birthdateThe citizen's name, gender and date of birth.
addressaddressPostal address. Its own scope — profile does not include it.
emailemail, email_verifiedContact 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.
phonephone_numberMobile number in E.164.
nicnic, nic_verifiedNational identity card number. Restricted — see below.
tintinPersonal 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.
rolesrolesPlatform 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.

Hosted verification
Merchant APIVerifiaCitizenPOST /sessions (workflowId) → linkPOST /sessions (workflowId) → linkshare link (SMS / WhatsApp / email)share link (SMS / WhatsApp / email)open link · fill fieldsopen link · fill fieldsPOST /sessions/:token/artifacts (selfie, ID)POST /sessions/:token/artifacts (selfie, ID)submit → steps run (NIC, FACE, AML…)submit → steps run (NIC, FACE, AML…)OTP prompt when a step needs itOTP prompt when a step needs itwebhook: run PASSED / REVIEW / FAILEDwebhook: run PASSED / REVIEW / FAILED

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.

Bill issue → payment → reconciliation
Biller ERPVerifiaCitizenBankPOST /bills (x-api-key) → ocr, payLinkPOST /bills (x-api-key) → ocr, payLinkdeliver pay-link (SMS / WhatsApp)deliver pay-link (SMS / WhatsApp)open /pay/<token>open /pay/<token>link account once (bank OTP at link time)link account once (bank OTP at link time)pay (saved account → AnyBanQ SMS OTP)pay (saved account → AnyBanQ SMS OTP)debit via JustPay tokendebit via JustPay tokenwebhook bill.paid {ocr}webhook bill.paid {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.

First-time link vs token reuse
Your AppVerifiaBankCitizenstart bank verify (bankCode, account)start bank verify (bankCode, account)register account (JustPay)register account (JustPay)SMS OTP to bank-registered numberSMS OTP to bank-registered numberenter OTP → account token storedenter OTP → account token storedlater: re-verify same customerlater: re-verify same customerAnyBanQ SMS OTP (token reused, no bank call)AnyBanQ SMS OTP (token reused, no bank call)verified (accountMask, token kept server-side)verified (accountMask, token kept server-side)

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):

  1. Upload the PDF (unprotected — remove any open/permission password first).
  2. Add signers in order; each gets a name, an invite channel (email/SMS) and a method (AnybanQID or draw/type). Optionally enter their NIC.
  3. Place fields — drag Signature, Name, NIC, Date or Text onto the page and assign each to a signer.
  4. 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):

Signing with AnybanQID
Signer linkVerifiaAnybanQID appopen /sign/<token>, fill required fieldsopen /sign/<token>, fill required fieldstap “Sign with AnybanQID”tap “Sign with AnybanQID”SIGNING request (doc name + hash)SIGNING request (doc name + hash)approve with Face ID → device key signs the hashapprove with Face ID → device key signs the hashnext signer, or certified PDF + verify codenext signer, or certified PDF + verify code

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.

Coming soon on theApp StoreComing soon onGoogle Play

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-001Challenge expiredOrder/challenge TTL elapsed. Create a new order.
AnybanQID-AUTH-002Signature invalidDevice signature failed verification.
AnybanQID-AUTH-003Replay detectedChallenge already consumed.
authorization_pendingCIBA pendingUser has not approved yet — keep polling.
invalid_grantInvalid grantCode/refresh token expired, reused, or revoked.
slow_downPoll too fastIncrease the CIBA poll interval.