QR Code Tiket Acara: Cara Membuat dan Scan QR lewat API (Node.js dan Python)

Oleh , Tim Engineering APIPedia · Terbit · Diperbarui · 10 menit baca

Buat QR tiket acara bertanda tangan HMAC lewat POST /v1/qr/generate, lalu scan saat check-in dengan /v1/qr/decode. Ada contoh curl, Node.js, dan Python.

Sampul APIPedia: judul "Tiket acara ber-QR, dibuat dan dipindai lewat API" dengan chip POST /v1/qr/generate dan POST /v1/qr/decode, serta kartu e-tiket A-0042 berisi QR dan tiga centang check-in: QR terbaca, tanda tangan cocok, belum pernah dipakai.

QR code tiket acara bisa dibuat dan dipindai dengan dua endpoint QR Code Generator APIPedia: POST /v1/qr/generate menghasilkan gambar QR dari nomor tiket, dan POST /v1/qr/decode membaca isinya kembali dari foto saat check-in. Produk ini tercatat di tier Free pada katalog, menerima JSON, dan mengembalikan PNG sebagai data URL.

Tutorial ini memandu Anda membangun alur tiket dari awal sampai pintu masuk: menyusun isi QR yang tidak bisa dipalsukan, membuat gambarnya dengan curl dan Node.js, membaca foto QR dengan Node.js dan Python, lalu menolak tiket ganda. Semua contoh memakai kunci apk_xxx dan data fiktif, dan bentuk contoh responsnya mengikuti panggilan nyata ke API.

Ringkasan cepat: apa yang perlu Anda tahu?

QR Code Generator APIPedia punya dua endpoint POST di bawah https://api.apipedia.id/v1/qr. Tabel berikut merangkum parameter dan batas yang paling sering dicari.

ItemDetail
Buat QRPOST https://api.apipedia.id/v1/qr/generate
Baca QRPOST https://api.apipedia.id/v1/qr/decode
AutentikasiHeader Authorization: Bearer apk_xxx
Isi QRField text, maksimal sekitar 2.900 karakter; content_type juga mendukung url, vcard, dan wifi
Ukuran gambarsize 64 sampai 2048 piksel, default 512
Koreksi galaterror_correction: L, M (default), Q, atau H
Output generatePNG di dalam data.image_data_url (data URL base64, bukan tautan CDN)
Input decodeimage_base64: PNG, JPEG, WebP, GIF, atau BMP, maksimal 8 MB
Simbol yang dibacaQR Code, Micro QR, dan rMQR; sampai 50 simbol per gambar lewat max_symbols
TierFree di katalog APIPedia

Masalah apa yang diselesaikan QR tiket lewat API?

QR tiket lewat API menyelesaikan dua pekerjaan sekaligus: membuat gambar QR tanpa memasang pustaka gambar di server, dan membaca foto QR tanpa aplikasi pemindai khusus. Panitia seminar, workshop kampus, atau konser komunitas biasanya hanya punya formulir pendaftaran dan grup WhatsApp. Daftar hadir dicentang manual, antrean panjang, dan tangkapan layar tiket mudah diteruskan ke orang lain.

Dengan dua endpoint ini, backend Anda cukup mengirim teks untuk mendapat PNG, lalu mengirim foto untuk mendapat teksnya kembali. Logika penting seperti tanda tangan dan status "sudah dipakai" tetap berada di server Anda, sehingga Anda memegang kendali penuh atas siapa yang boleh masuk.

Diagram dua jalur tiket QR. Jalur penerbitan: 1) pesanan dibayar, nomor tiket A-0042 dibuat; 2) server menghitung tanda tangan HMAC-SHA256 dan mengambil 16 karakter; 3) POST /v1/qr/generate dengan text, size 512, error_correction Q; 4) PNG dari image_data_url dikirim ke peserta. Jalur check-in: 1) petugas memotret QR di pintu; 2) POST /v1/qr/decode dengan image_base64; 3) server menghitung ulang tanda tangan dan membandingkan; 4) nomor tiket ditandai terpakai di database. Catatan: kunci apk_xxx dan TICKET_SECRET hanya berada di server.

Apa yang perlu disiapkan sebelum mulai?

Anda hanya perlu satu kunci API APIPedia dan satu rahasia milik server sendiri. Daftar atau masuk di console.apipedia.id, buka menu Token API, lalu buat kunci baru yang berawalan apk_.

  • APIPEDIA_KEY: kunci apk_xxx untuk memanggil API. Simpan di variabel lingkungan server, jangan di kode browser atau aplikasi petugas.
  • TICKET_SECRET: string acak panjang buatan Anda sendiri, misalnya hasil openssl rand -hex 32. Rahasia ini dipakai menandatangani tiket dan tidak pernah dikirim ke APIPedia.
  • Node.js 18 ke atas atau Python 3 dengan pustaka requests.

Langkah 1: bagaimana menyusun isi QR yang tidak bisa dipalsukan?

Isi QR yang aman memuat nomor tiket ditambah tanda tangan HMAC, bukan nomor tiket saja. QR hanyalah teks yang digambar, jadi siapa pun bisa membuat QR berisi A-0043 kalau polanya tertebak. Tanda tangan membuat tiket palsu ditolak karena pemalsu tidak tahu TICKET_SECRET.

Format yang dipakai di tutorial ini adalah empat bagian dipisah titik:

TIKET.<id-acara>.<nomor-tiket>.<tanda-tangan>
TIKET.EVT-DEMO-2026.A-0042.ddaa8042e43d8de0

Tanda tangan adalah 16 karakter pertama dari HMAC-SHA256 atas EVT-DEMO-2026.A-0042. Hindari titik di dalam id acara dan nomor tiket supaya pemisahnya tidak ambigu. Jangan menaruh nama, email, atau nomor telepon peserta di dalam QR, karena isinya bisa dibaca oleh pemindai mana pun.

Langkah 2: bagaimana membuat gambar QR tiket?

Gambar QR dibuat dengan mengirim teks tiket ke POST /v1/qr/generate. Pakai error_correction bernilai Q agar QR tetap terbaca walau layar ponsel peserta retak atau cetakannya sedikit kotor, dan size 512 supaya tajam di layar maupun kertas.

curl -X POST https://api.apipedia.id/v1/qr/generate \
  -H "Authorization: Bearer apk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "TIKET.EVT-DEMO-2026.A-0042.ddaa8042e43d8de0",
    "size": 512,
    "error_correction": "Q"
  }'

Contoh respons generate

Respons berikut berasal dari panggilan nyata; isi image_data_url dipotong karena panjangnya ribuan karakter.

{
  "is_success": true,
  "message": "QR code berhasil dibuat",
  "data": {
    "content_type": "text",
    "format": "png",
    "size": 512,
    "error_correction": "Q",
    "image_data_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
  }
}

Potong awalan data:image/png;base64,, dekode sisanya, lalu simpan sebagai berkas PNG atau tempel langsung di atribut src gambar pada halaman tiket. Kalau tiketnya ingin dikirim sebagai dokumen, alurnya bisa dilanjutkan dengan tutorial HTML ke PDF.

Versi Node.js: buat dan simpan PNG

Modul berikut memuat seluruh alur, termasuk fungsi check-in yang dibahas di langkah berikutnya. Logika tanda tangan dan check-in-nya sudah diuji dengan data contoh di atas.

// tiket.mjs - Node.js 18+ (fetch bawaan)
import crypto from "node:crypto";
import fs from "node:fs/promises";

const API = "https://api.apipedia.id/v1/qr";
const KEY = process.env.APIPEDIA_KEY;      // apk_xxx
const SECRET = process.env.TICKET_SECRET;  // rahasia milik server Anda

function tandaTangan(eventId, nomor) {
  return crypto.createHmac("sha256", SECRET)
    .update(`${eventId}.${nomor}`)
    .digest("hex")
    .slice(0, 16);
}

async function panggil(path, body) {
  const res = await fetch(`${API}${path}`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const json = await res.json();
  if (!json.is_success) throw new Error(`${res.status}: ${json.message}`);
  return json.data;
}

// Langkah 1 + 2: susun isi tiket, minta gambar QR, simpan PNG
export async function buatQrTiket(eventId, nomor) {
  const text = `TIKET.${eventId}.${nomor}.${tandaTangan(eventId, nomor)}`;
  const data = await panggil("/generate", {
    text,
    size: 512,
    error_correction: "Q",
  });
  const base64 = data.image_data_url.split(",")[1];
  await fs.writeFile(`tiket-${nomor}.png`, Buffer.from(base64, "base64"));
  return text;
}

// Langkah 3 + 4: baca foto QR, periksa tanda tangan, tandai terpakai
export async function checkIn(fotoPath, sudahDipakai) {
  const image_base64 = (await fs.readFile(fotoPath)).toString("base64");
  const data = await panggil("/decode", { image_base64, max_symbols: 1 });
  if (data.count === 0) return { ok: false, alasan: "QR tidak terbaca" };

  const [awalan, eventId, nomor, sig] = data.symbols[0].text.split(".");
  if (awalan !== "TIKET" || !sig) return { ok: false, alasan: "Bukan tiket acara ini" };

  const benar = tandaTangan(eventId, nomor);
  const cocok = sig.length === benar.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(benar));
  if (!cocok) return { ok: false, alasan: "Tanda tangan tidak cocok" };

  if (sudahDipakai.has(nomor)) return { ok: false, alasan: "Tiket sudah dipakai" };
  sudahDipakai.add(nomor);
  return { ok: true, eventId, nomor };
}

Langkah 3: bagaimana membaca QR saat check-in?

QR dibaca dengan mengirim foto dalam bentuk base64 ke POST /v1/qr/decode. Petugas memotret layar ponsel atau tiket cetak peserta dari halaman check-in milik Anda, backend meneruskan fotonya, dan API mengembalikan teks di dalam data.symbols[0].text.

# ubah foto QR menjadi base64 satu baris, lalu kirim
B64=$(base64 -w0 tiket-A-0042.png)

curl -X POST https://api.apipedia.id/v1/qr/decode \
  -H "Authorization: Bearer apk_xxx" \
  -H "Content-Type: application/json" \
  -d "{\"image_base64\": \"$B64\", \"max_symbols\": 1}"

Contoh respons decode

Respons berikut mengikuti bentuk respons nyata untuk QR di atas dan sudah dipangkas: koordinat position, bytes_hex, dan beberapa field identitas simbol dihilangkan agar ringkas.

{
  "is_success": true,
  "message": "1 barcode berhasil dibaca",
  "data": {
    "count": 1,
    "symbols": [
      {
        "format": "QRCode",
        "format_label": "QR Code",
        "text": "TIKET.EVT-DEMO-2026.A-0042.ddaa8042e43d8de0",
        "content_type": "Text",
        "encoding": "ascii",
        "encoding_source": "guessed",
        "has_eci": false,
        "text_is_binary": false,
        "bytes_length": 43,
        "bytes_base64": "VElLRVQuRVZULURFTU8tMjAyNi5BLTAwNDIuZGRhYTgwNDJlNDNkOGRlMA==",
        "orientation": 0,
        "is_mirrored": false,
        "is_inverted": false,
        "parsed": { "type": "text" }
      }
    ],
    "formats_searched": ["QRCode", "MicroQRCode", "RMQRCode"],
    "image": { "mime": "image/png", "bytes": 3660 }
  }
}

Dua field yang paling penting adalah count dan symbols[0].text. Gambar tanpa QR bukan error: API tetap menjawab 200 dengan count 0, symbols kosong, dan sebuah hint. Field try_harder aktif secara default, sehingga foto miring atau terbalik tetap dicoba dibaca.

Langkah 4: bagaimana memverifikasi tiket dan menolak tiket ganda?

Tiket diverifikasi dengan menghitung ulang tanda tangan di server lalu mencocokkan nomor tiket dengan catatan "sudah dipakai" di database. API hanya membaca gambar; keputusan boleh masuk atau tidak sepenuhnya ada di kode Anda. Fungsi checkIn pada modul Node.js di atas sudah melakukannya, dan berikut padanannya dalam Python.

# checkin.py - pip install requests
import base64, hashlib, hmac, os, requests

API = "https://api.apipedia.id/v1/qr/decode"
KEY = os.environ["APIPEDIA_KEY"]            # apk_xxx
SECRET = os.environ["TICKET_SECRET"].encode()

def tanda_tangan(event_id, nomor):
    pesan = f"{event_id}.{nomor}".encode()
    return hmac.new(SECRET, pesan, hashlib.sha256).hexdigest()[:16]

def check_in(foto_path, sudah_dipakai):
    with open(foto_path, "rb") as f:
        image_base64 = base64.b64encode(f.read()).decode()
    res = requests.post(
        API,
        headers={"Authorization": f"Bearer {KEY}"},
        json={"image_base64": image_base64, "max_symbols": 1},
        timeout=15,
    )
    body = res.json()
    if not body["is_success"]:
        raise RuntimeError(f"{res.status_code}: {body['message']}")
    data = body["data"]
    if data["count"] == 0:
        return {"ok": False, "alasan": "QR tidak terbaca"}

    bagian = data["symbols"][0]["text"].split(".")
    if len(bagian) != 4 or bagian[0] != "TIKET":
        return {"ok": False, "alasan": "Bukan tiket acara ini"}
    _, event_id, nomor, sig = bagian

    if not hmac.compare_digest(sig, tanda_tangan(event_id, nomor)):
        return {"ok": False, "alasan": "Tanda tangan tidak cocok"}
    if nomor in sudah_dipakai:
        return {"ok": False, "alasan": "Tiket sudah dipakai"}
    sudah_dipakai.add(nomor)
    return {"ok": True, "event_id": event_id, "nomor": nomor}

print(check_in("tiket-A-0042.png", set()))

Di produksi, ganti set() dengan kolom checked_in_at di tabel tiket dan lakukan pembaruan secara atomik, misalnya UPDATE ... WHERE checked_in_at IS NULL. Dengan begitu dua petugas yang memindai tiket yang sama pada detik yang sama tidak sama-sama meloloskannya.

Matriks empat hasil check-in tiket QR. Hijau, masuk: count 1, tanda tangan cocok, nomor belum dipakai. Amber, tiket sudah dipakai: tanda tangan cocok tetapi nomor sudah tercatat, arahkan ke meja bantuan. Merah, tanda tangan tidak cocok: isi QR diubah atau dibuat pihak lain, tolak. Amber, QR tidak terbaca: count 0 dan ada hint, minta peserta menaikkan kecerahan layar lalu foto ulang.

Kesalahan apa yang paling sering terjadi?

Kesalahan paling sering adalah mengirim body yang tidak lengkap atau gambar yang tidak bisa dibaca. Semua error memakai amplop yang sama dengan is_success bernilai false dan data bernilai null, dan pesannya menyebut field yang bermasalah.

{
  "is_success": false,
  "message": "Field \"text\" wajib diisi.",
  "data": null
}
  • 400 saat generate: field text kosong, size di luar 64 sampai 2048, atau isi melebihi kapasitas QR. Pendekkan isi tiket; cukup id dan tanda tangan.
  • 400 saat decode: base64 rusak, format gambar tidak didukung, atau berkas di atas 8 MB. Perkecil foto di sisi klien sebelum dikirim.
  • 401: kunci salah, kedaluwarsa, atau header Authorization tidak terkirim.
  • 403: kunci valid tetapi scope-nya tidak mencakup endpoint QR. Ubah scope di halaman Token.
  • count 0: bukan error. Foto terlalu gelap, buram, atau terpotong. Minta peserta menaikkan kecerahan layar lalu foto ulang.
  • Kunci di aplikasi petugas: jangan memanggil API langsung dari browser petugas. Lewatkan foto ke backend agar apk_xxx dan TICKET_SECRET tidak bocor.
  • Membandingkan tanda tangan dengan ==: pakai crypto.timingSafeEqual atau hmac.compare_digest seperti di contoh.

Pertanyaan yang sering diajukan

Apakah QR Code Generator APIPedia gratis?

Ya, produk QR Code Generator tercatat di tier Free pada katalog APIPedia. Anda tetap perlu kunci API dari console untuk memanggilnya, dan setiap panggilan tercatat di pemakaian akun.

Apakah gambar QR disimpan di server APIPedia?

Tidak ada tautan gambar yang dikembalikan. PNG dikirim langsung di dalam respons sebagai data URL pada image_data_url, jadi Anda yang memutuskan menyimpannya di storage sendiri atau membuatnya ulang saat dibutuhkan.

Bisakah endpoint decode membaca QRIS atau barcode produk?

Endpoint /v1/qr/decode membaca QR Code, Micro QR, dan rMQR, dan mengenali isi seperti URL, Wi-Fi, vCard, dan QRIS pada field parsed. Untuk barcode batang seperti EAN-13 atau Code 128, pakai produk Barcode Generator & Reader di /v1/barcode/decode.

Tingkat koreksi galat mana yang cocok untuk tiket?

Tingkat Q cocok untuk kebanyakan tiket karena menyeimbangkan ketahanan dan kepadatan pola. Pakai H bila QR dicetak kecil di gelang atau kartu yang mudah tergores, dan M bila isinya panjang dan hanya ditampilkan di layar.

Apakah tiket tetap aman kalau pesertanya membagikan tangkapan layar?

Tanda tangan mencegah tiket palsu, bukan tiket yang disalin. Salinan ditangani oleh pencatatan "sudah dipakai": orang pertama yang memindai masuk, pemindaian berikutnya dengan nomor yang sama ditolak.

Lanjut ke mana setelah ini?

Langkah berikutnya adalah mencoba kedua endpoint dengan kunci Anda sendiri di console.apipedia.id; spesifikasi lengkapnya ada di halaman produk QR Code Generator. Untuk alur acara yang lebih lengkap, baca juga OCR KTP untuk verifikasi identitas peserta, cara kerja webhook untuk menerima notifikasi pembayaran, dan menghubungkan APIPedia ke Claude lewat MCP bila Anda ingin agen AI yang membuat QR-nya.

Topik:

Tentang penulis

Tim APIPedia — Tim Engineering APIPedia

Tim engineering di balik APIPedia yang membangun dan memelihara API data Indonesia - ongkir, OCR KTP, data wilayah, BMKG, hingga server MCP - dan menulis panduan integrasinya dari pengalaman langsung.