Documentation

Offline sign

A user can approve even when the phone has no internet: your page shows a QR, the phone reads it offline, shows your brand and the transaction details, asks for Face ID and produces a 7-character code. The user types it into your page and Touchque verifies it. The QR content is encrypted for the user’s phone only: any other QR reader or JWT decoder sees just ciphertext. A code is bound to one challenge and works once.

POST/offline/challenge
EndpointBodyResult
POST /offline/challengeexternalUsername, type, details?, clientIp?, userAgent?, ttlSeconds?QR text (qr), ready QR image (qrDataUrl), challengeId, expiry, totpAvailable
POST /offline/verifychallengeId, code{ approved: true, externalUsername, type } — 401 + attemptsLeft on a wrong code
POST /offline/totp/verifyexternalUsername, code, type?, clientIp?Time-based code (lower assurance). 403 for critical actions
Turn on “Offline sign (QR + code)” in the dashboard under Developers → App user policy → Offline (off by default). Critical actions also need “Allow offline sign for critical actions” and always require transaction details. The time-based code is its own switch on the same tab, “Offline time-based code (lower assurance)”, and needs offline sign on.
Limits: the code is made without the phone’s live risk check, and a phishing page can show a real QR unchanged (the phone shows the brand, location and device). It is MAC-based: only Touchque can verify a code. A challenge locks after 5 wrong tries, and there is an hourly failed-attempt limit per user.
Offline time-based code (optional)

When “Offline time-based code” is on in the policy, the app shows a 7-character code that rotates every 30 seconds, no camera or QR needed. It carries no transaction details and can be relayed within its window, so it is lower assurance and never accepted for critical actions. Each code works once.

Challenge + verify
const { Touchque } = require('@touchque/node');
const tq = new Touchque({ apiKey: 'tq_abc123', apiSecret: 'YOUR_API_SECRET' });

// 1) Create a challenge when the user chooses offline verification
const challenge = await tq.offline.challenge({
  externalUsername: 'user@yourcompany.com',
  type: 'WITHDRAW',                       // Action Type from your Dashboard
  details: { Amount: '5.000 TRY', To: 'TR12 0006 …' },
  clientIp: req.ip,
  userAgent: req.get('user-agent'),
});

// Show it on your page:
// <img src={challenge.qrDataUrl} />   (or draw challenge.qr with your own QR renderer)

// 2) Verify the 7-character code the user types from the phone
const result = await tq.offline.verify({ challengeId: challenge.challengeId, code: 'K7M-2QXP' });
if (result.approved) { /* complete the action */ }
else { /* result.reason, result.attemptsLeft */ }
Response 200
{
  "challengeId": "c0ffee…",
  "qr": "TQ2.k1.pKq3…",
  "qrDataUrl": "data:image/png;base64,…",
  "expiresAt": "2026-09-26T12:02:00.000Z",
  "expiresInSeconds": 120,
  "totpAvailable": false
}
Time-based code
// Lower assurance: no QR, no transaction details. Refused for critical actions.
const result = await tq.offline.verifyTotp({
  externalUsername: 'user@yourcompany.com',
  code: 'K7M-2QXP',
  type: 'LOGIN',
  clientIp: req.ip,
});
// result = { approved: true } | { approved: false, reason: 'invalid_code' | 'offline_totp_disabled' | … }