Documentation

Troubleshooting

The push did not arrive, what a case code (K7P-2QX) means, a user changed or lost their phone, the QR does not link, the webhook does not come: the causes and what to do.

The push didn't arrive

Go through these in order. Most of the time the first one is enough.

  1. Open Touchque Authenticator. When the app connects, it gets every request that is still pending and not expired, so the request shows up in the app even without the notification. A request lives 30 seconds; after that, send a new one.
  2. Notification permission. Notifications must be on for Touchque Authenticator in the phone's settings; Focus or Do Not Disturb can hide them.
  3. Internet. A push needs the phone online. Without internet, use offline sign: Offline sign.
  4. Was the request sent at all? Look at the login.request answer. 404: the user has no linked phone, so no push went out. 403 blocked: the risk engine refused it (look up the case code). 429 rate_limited: the limit of 5 pushes per user in 10 minutes is reached. 423 frozen: 2FA is frozen after many rejections. The dashboard's Logs page shows every request and how it ended.
  5. The right phone? The push goes to the phone the user linked. If the app was reinstalled or the phone restored from a backup, its keys are gone (403 device_keys_lost): the user links the phone again.
While the user waits for the push, show a way out on your page: "No notification? Open Touchque Authenticator", and if needed "I can't reach my phone" (recovery code).

Case codes (K7P-2QX): what they are and how to look one up

Every login.request answer and every refusal (403 blocked, 403 device_keys_lost, 423 frozen, 429 rate_limited) carries a caseCode: 6 characters such as K7P-2QX. It names the decision Touchque made for that request. The SDKs put it on the error too (err.caseCode).

  1. Show the code on your refusal screen: "Tell your support team: K7P-2QX".
  2. Your team opens Logs in the Touchque dashboard and types the code into the Look up a case field at the top.
  3. The decision opens: the reasons, each check that ran, the rule that matched and what would have let the request through.

Number-matching and passkey answers also carry plain reasons ([{ code, text }]) you can show to the user as they are.

New phone or lost phone

The user has a new phone and still has the old one

Nothing to do on your side. On the old phone, the user opens Touchque Authenticator and starts the transfer ("move your accounts to the new phone"), scans the transfer QR with the new phone and types the number the new phone shows. The selected accounts move, signed by the old phone, and are removed from it. The next push goes to the new phone.

The phone is lost, broken or stolen

Offer these in order, from the safest:

  1. A recovery code. One of the 10 codes you showed at enrolment approves a pending sign-in request. Critical actions and sign-ins that need a passkey or the phone cannot be approved with a recovery code. After signing in, the user links their new phone.
  2. A passkey. If the user registered a passkey on your site, they can sign in with it: Passkeys.
  3. Moving to a new phone with colleagues vouching (Never Locked Out). After the password step, recovery.start gives you a QR that the new phone scans. At least two colleagues the user named approve on their own phones, a cooling-off period passes (the old phone and the vouchers can stop it meanwhile) and the account moves to the new phone. Critical actions stay closed for the next 24 hours.
  4. A reset by you. The last resort. Check strongly who the user is, your own way (fraudsters aim at exactly this step), then reset the secret: the old phone can no longer approve, you get a new QR and new recovery codes, and a frozen 2FA opens again.
Approve with a recovery code
// The user chose "I can't reach my phone" and typed a recovery code;
// pending.requestId is the sign-in request you sent in step 3.
await tq.login.approveWithRecoveryCode({ requestId: pending.requestId, code: typedCode });
Move to a new phone (Node.js)
// After the password step, the user chose "Move me to my new phone":
const r = await tq.recovery.start({ externalUsername: user.email, clientIp: req.ip, displayName: user.name });
showQr(r.bindUri); // Touchque Authenticator on the new phone scans it

// While the user waits:
const now = await tq.recovery.status(r.recoveryId);
// AWAITING_DEVICE → VOUCHING → COOLING → COMPLETED (or VETOED, EXPIRED, FAILED)
Reset the secret
// Only after you have checked who the user is, your own way:
const { qrCodeDataUrl, recoveryCodes, expiresAt } = await tq.auth.resetSecret({ externalUsername: user.email });
// The old phone can no longer approve. Show the new QR and the new recovery codes.

The QR code doesn't link the phone

  • A secret is valid for 5 minutes. An expired QR does not work (410): create a new secret.
  • A secret works once (409). If a phone is already linked, reset first instead of creating another secret.
  • Scan the QR from inside Touchque Authenticator, not with the phone's camera app.
  • Check the link with getUser: once linked it has used: true and a deviceId.

The webhook doesn't arrive or its signature fails

  • The Callback URL must be set under Developers → Webhooks and be HTTPS (localhost is fine for local tests).
  • Answer 2xx within 5 seconds; otherwise Touchque retries a few times. Do the heavy work after answering.
  • Verify the signature over the raw body: do not parse the JSON and write it again. In Express, use express.raw on that route.
  • The signature is made with this integration's API secret; the Signing secret under Developers → Webhooks is the same value. If you rotated the key, update the secret on your server too.
  • The same event can arrive twice: de-duplicate on jti or requestId. If the webhook never comes, you can carry on with status checks (login.status).

API calls answer 401 or 403

  • 401: something is missing or stale; the answer's error names it. No x-api-key header, or SIGNATURE_REQUIRED, TIMESTAMP_REQUIRED, TIMESTAMP_EXPIRED (x-timestamp is more than 5 minutes off the server's clock: sync it with NTP), NONCE_REQUIRED, NONCE_INVALID (the nonce was used before or is shorter than 16 characters). A 401 never means a wrong signature.
  • 403 SIGNATURE_INVALID: the signature did not match. Use the right secret (the new one if you rotated the key); the signed path is exactly the one you call, with its query string; the body is byte for byte the one you send; GET requests are signed with an empty body. The SDKs do all of this for you. See Authentication.
  • 403 for the key itself: Invalid API key (no such key), Credential has been revoked, credential_expired (rotate it in the dashboard), credential_org_access_revoked (the person who made the key is no longer in the organization), insufficient_scope (the key has no scope for this area).
  • 403 IP not allowed: if you use an IP allowlist, your server's outgoing address must be on it. Other codes: Error codes.

Still stuck? Write to support@touchque.com with the case code or the requestId.