Generate QR codes for event check-in

Turn a ticket into a scannable check-in code: generate the image with one POST, save it before its signed link expires, and make sure a forged code gets nobody through the door.

QR codes are everywhere because they solve one problem well: getting a string from paper or a screen into a phone without anyone typing it. The string is usually a URL, and the useful projects are the ones where that URL does something: checks a guest in, opens a menu, joins a wifi network.

You will build the ticketing half of an event check-in system. Each ticket gets a URL that is signed so it cannot be guessed or forged, each URL is turned into a QR code image, the image is saved to disk, and a page displays it. At the door, scanning the code hits your server, which verifies the signature and marks the guest as arrived. Along the way you will deal with the one thing about this API that catches people out: the image link it gives you does not last.

What the API returns

QR Code Generator (qrcodegenerator) is a POST endpoint that takes a JSON body. One parameter is required: value, the text to encode. It does not return image bytes or a base64 string. It returns metadata and a link:

FieldWhat it is
idIdentifier for this image, also used as the download filename
formatImage format, png by default
typeThe kind of data encoded, text by default
size, marginDimensions in pixels
expiresUnix timestamp in milliseconds when the download link stops working
downloadURLA signed URL for the image file

The reference also lists a correction field for the error-correction level. Treat it as optional; not every response includes it.

Try it liveQR Code Generator10 credits
POSThttps://api.apiverve.com/v1/qrcodegenerator
curl -X POST "https://api.apiverve.com/v1/qrcodegenerator" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value":"https://example.com/checkin?t=T-10482","margin":10}'
Sign in or create a free account to run this with your own key.

Compare expires with the current time: the link is temporary, so save the image.

When we called it, expires was almost exactly 48 hours after the request. Do not hardcode that; read the field. The point is the shape of the contract: the API generates the image and hosts it briefly; keeping it is your job.

Most styling options are premium. On plans that include them you can set size (50 to 2048 pixels), format (png and jpeg, with svg and webp marked as premium vector formats), color and backgroundColor, dot and corner styles, a gradient, and a logo in the centre. The type parameter is premium too, and its advanced values wifi and vcard are called out specifically. On every plan you get value and margin. Premium parameters sent on a lower plan are ignored rather than rejected, so code written against a paid plan still runs; it just produces the default black-on-white PNG.

The catalog lists this API at 10 credits per call.

Step 1: generate and save, in Node and Python

Generation is one request. Saving is a second, plain GET to downloadURL. That second request goes to a storage host, not to APIVerve, so do not send your API key with it. The URL already carries its own signature, and your key has no business leaving your server for anywhere else.

import { writeFile } from 'node:fs/promises';

const BASE = 'https://api.apiverve.com/v1';

export async function createQr(value, options = {}) {
  const res = await fetch(`${BASE}/qrcodegenerator`, {
    method: 'POST',
    headers: { 'x-api-key': process.env.APIVERVE_API_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ value, ...options }),
    signal: AbortSignal.timeout(15000),
  });
  const body = await res.json().catch(() => null);
  if (!res.ok || body?.status !== 'ok') {
    const outOfCredits = res.status === 429 && res.headers.get('x-api-remaining-credits') === '0';
    throw new Error(outOfCredits ? 'Out of credits' : body?.error || `HTTP ${res.status}`);
  }
  return body.data;
}

export async function saveQr(qr, path) {
  if (Date.now() >= qr.expires) throw new Error(`Download link for ${qr.id} has expired`);
  const res = await fetch(qr.downloadURL, { signal: AbortSignal.timeout(15000) }); // no API key here
  if (!res.ok) throw new Error(`Download failed: HTTP ${res.status}`);
  await writeFile(path, Buffer.from(await res.arrayBuffer()));
  return path;
}

In Python, json= serialises the body and sets the Content-Type header for you. In Node you set both yourself. Note the units in the expiry check: expires is milliseconds, which matches JavaScript's Date.now() but not Python's time.time(), which is seconds. Mixing those up gives you a check that is wrong by a factor of a thousand, and in this case one that would never fire.

Check yourself

You store the downloadURL in your database and use it as the img src on ticket pages. What goes wrong?

Answer the check above to continue.

Run every example on your own key. Free plan, no card.

Scaling up?

Volume pricing, custom SLAs, and dedicated support for high-traffic teams.

Contact sales