BankID — QR code
Show BankID's animated QR code. Cerrtify gives you the QR data for every second; you only draw it.
When the user opens BankID on another device (usually their phone), you show a QR code. BankID requires an animated QR: its content changes every second, and a screenshot of an old code stops working. You don't need to compute anything — Cerrtify hands you the current QR data.
In short
Start the order, then call GET /service/bankid/qr-code?orderRef=… about once a second and draw the returned qrData as a QR code. Stop when /collect says complete or failed. The QR endpoint is free — only the BankID order itself is billed.
How the pieces fit
Browser ──every 1 s──▶ your backend ──▶ GET /service/bankid/qr-code (Cerrtify)
Browser ◀── qrData ─── your backend
Browser ──every 2 s──▶ your backend ──▶ POST /service/bankid/collect (Cerrtify)Your API key never goes to the browser. The browser asks your backend; your backend calls Cerrtify with the key.
1. Start the order
POST /service/bankid/auth (sign in), POST /service/bankid/sign (sign) or POST /service/bankid/payment (payment). The response holds the orderRef:
{
"orderRef": "131daac9-16c6-4618-beb0-365768f37288",
"autoStartToken": "7c40b5c9-fa74-49cf-b98c-bfe651f9a7c6",
"qrStartToken": "67df3917-fa0d-44e5-b327-edcc928297f8",
"qrStartSecret": "d28db9a7-4cde-429e-a983-359be676944c"
}2. Get the QR data every second
curl "https://sandbox-api.cerrtify.com/service/bankid/qr-code?orderRef=131daac9-16c6-4618-beb0-365768f37288" \
-H "X-API-Key: cert_..."{ "qrData": "bankid.67df3917-fa0d-44e5-b327-edcc928297f8.4.dee5cd3f9b..." }Put qrData into any QR library as plain text — for example qrcode (npm), qrcode.react, or segno / qrcode (Python). Replace the image every second.
// Backend (Node/Express): the browser calls GET /bankid/qr?orderRef=… every second
app.get("/bankid/qr", async (req, res) => {
const r = await fetch(
`https://sandbox-api.cerrtify.com/service/bankid/qr-code?orderRef=${encodeURIComponent(req.query.orderRef)}`,
{ headers: { "X-API-Key": process.env.CERRTIFY_API_KEY } },
);
res.status(r.status).json(await r.json()); // { qrData: "bankid.…" }
});3. Stop
Poll POST /service/bankid/collect every 2 seconds (auth flow). Stop the QR loop when the status is complete or failed, when the user has scanned (hintCode userSign), or after about 5 minutes — BankID orders and QR sessions expire.
Errors
| Response | Meaning |
|---|---|
400 missing_orderRef | No orderRef in the query string. |
400 no_active_qr_session | The order is unknown, older than 5 minutes, belongs to another organisation, or was started with ?async=true (see below). Start a new order. |
429 | Rate limit. One QR call per second per open order is fine; count it in when you set the key's limits. |
Computing the QR yourself (optional)
For orders started with ?async=true, or if you'd rather not make one call per second, compute the data from the start response. Keep qrStartSecret on your server.
qrData = "bankid." + qrStartToken + "." + seconds + "." + HMAC-SHA256(qrStartSecret, seconds) in lowercase hex, where seconds is the whole number of seconds since the order started (0, 1, 2 …).
import { createHmac } from "node:crypto";
const seconds = Math.floor((Date.now() - startedAt) / 1000);
const qrData = `bankid.${qrStartToken}.${seconds}.${createHmac("sha256", qrStartSecret).update(String(seconds)).digest("hex")}`;import hmac, hashlib, time
seconds = int(time.time() - started_at)
qr_data = f"bankid.{qr_start_token}.{seconds}." + hmac.new(qr_start_secret.encode(), str(seconds).encode(), hashlib.sha256).hexdigest()Same device instead of QR
If the user is already on the phone with BankID, skip the QR and open the app with the autoStartToken:
bankid:///?autostarttoken=<autoStartToken>&redirect=<your return URL, URL-encoded>Keep polling /collect when the user comes back.