Skip to content

Verifiabl-generated QR code

Use this alternative if your system cannot make the QR code. Prepare an AU/NZ v2 payslip with its jurisdiction-specific helper. Send its API-managed registration to Verifiabl. The request includes ciphertext and non-PII data, but not plaintext PII. Verifiabl returns a PNG QR code for your payslip.

If your system can generate and embed QR codes, use the recommended Generate the QR code in your system guide. The encrypted PII then stays in your environment.

Follow the Node, .NET or Ruby tabs below. For another platform, call the endpoints directly using the Issuer API reference.

npm install @verifiabl/issuer@0.30.0
dotnet add package Verifiabl.Issuer --version 0.15.0
gem install verifiabl-issuer --version 0.3.0

Ruby requires version 3.3 or newer. See the complete Ruby example.

Create a client with the client ID and the client secret from your onboarding. The SDK exchanges them for a short-lived access token, and gets a new token when necessary.

import { VerifiablClient } from "@verifiabl/issuer";

const client = new VerifiablClient({
  environment: "sandbox", // or "production"
  auth: {
    clientId: process.env.VERIFIABL_CLIENT_ID,
    clientSecret: process.env.VERIFIABL_CLIENT_SECRET,
  },
});
using Verifiabl;
using Verifiabl.Client;

var client = new VerifiablClient(new VerifiablClientOptions
{
    Environment = VerifiablEnvironment.Sandbox, // or Production
    Auth = VerifiablAuth.ClientCredentials(
        Environment.GetEnvironmentVariable("VERIFIABL_CLIENT_ID")!,
        Environment.GetEnvironmentVariable("VERIFIABL_CLIENT_SECRET")!),
});
require "verifiabl/issuer"

issuer = Verifiabl::Issuer::Client.new(
  Verifiabl::Issuer::Configuration.new(
    environment: :sandbox, # or :production
    client_id: ENV.fetch("VERIFIABL_CLIENT_ID"),
    client_secret: ENV.fetch("VERIFIABL_CLIENT_SECRET")
  )
)

To call the token endpoint directly, refer to Authentication in the reference. It shows the OAuth2 client-credentials exchange.

Give the jurisdiction-specific helper the employee’s PII, non-PII payslip data, issue time, and encryption key. The helper selects the matching schema and PII profile, validates non-PII fields, and encrypts locally. Do not log or store plaintext PII.

import { prepareAustralianV2Payslip } from "@verifiabl/issuer";

const encodedKey = process.env.VERIFIABL_ENCRYPTION_KEY_BASE64;
if (!encodedKey) throw new Error("Set VERIFIABL_ENCRYPTION_KEY_BASE64");
const key = Buffer.from(encodedKey, "base64");

const prepared = prepareAustralianV2Payslip({
  pii: {
    employeeName: "Jane A. Doe", employerName: "Example Payroll Pty Ltd",
    employerAbn: "12 345 678 901",
    address: { lines: ["12 Example St"], suburb: "Sydney", stateOrTerritory: "NSW", postcode: "2000" },
  },
  payslipNonPii: {
    periodEnd: "2026-08-31", paymentDate: "2026-09-04", currency: "AUD",
    gross: "9000.00", paygw: "2250.00", net: "6750.00",
  },
  issuedAt: new Date().toISOString(), key,
});
using Verifiabl;
using Verifiabl.Client;

byte[] key = Convert.FromBase64String(
    Environment.GetEnvironmentVariable("VERIFIABL_ENCRYPTION_KEY_BASE64")!);

PreparedV2Payslip prepared = V2Issuance.PrepareAustralian(
    pii: new AustralianPiiFields
    {
        EmployeeName = "Jane A. Doe", EmployerName = "Example Payroll Pty Ltd",
        EmployerAbn = "12 345 678 901",
    },
    payslip: new AustralianPayslipV2
    {
        PeriodEnd = new DateOnly(2026, 8, 31), PaymentDate = new DateOnly(2026, 9, 4),
        Currency = PayslipCurrencies.Aud,
        Gross = 9000.00m, Paygw = 2250.00m, Net = 6750.00m,
    },
    issuedAt: DateTimeOffset.UtcNow, key: key);
require "base64"

key = Base64.strict_decode64(ENV.fetch("VERIFIABL_ENCRYPTION_KEY_BASE64"))

prepared = Verifiabl::Issuer.prepare_australian_v2_payslip(
  pii: {
    employee_name: "Jane A. Doe", employer_name: "Example Payroll Pty Ltd",
    employer_abn: "12 345 678 901",
    address: {lines: ["12 Example St"], suburb: "Sydney", state_or_territory: "NSW", postcode: "2000"}
  },
  payslip_non_pii: {
    period_end: "2026-08-31", payment_date: "2026-09-04", currency: "AUD",
    gross: "9000.00", paygw: "2250.00", net: "6750.00"
  },
  issued_at: Time.now.utc, key: key
)

The .NET example uses DateOnly on .NET 8 or newer. On .NET Framework 4.7.2, use YYYY-MM-DD strings for payslip dates.

If you do not use the SDK, make the same pipe-delimited plaintext and use the same encryption. Refer to PII serialisation format and Encryption in the reference.

Send prepared.apiManagedRegistration (Node), prepared.ApiManagedRegistration (.NET), or prepared.api_managed_registration (Ruby) to registerAndBuildBarcode. The request includes non-PII data, encryption metadata, and ciphertext. It does not include the prepared self-built reference. Verifiabl returns a PNG and its own reference. Do not replay an ambiguous failure as an idempotent retry.

// This request omits prepared.verifiablReference.
const result = await client.registerAndBuildBarcode(prepared.apiManagedRegistration);
// Store result.verifiablReference with the payslip; result.barcode.data is a base64 PNG.
// This request omits prepared.VerifiablReference.
RegisterAndBuildBarcodeResponse result = await client.RegisterAndBuildBarcodeAsync(
    prepared.ApiManagedRegistration);
// Store result.VerifiablReference with the payslip; result.Barcode.Data is a base64 PNG.
# This request omits prepared.verifiabl_reference.
result = issuer.register_and_build_barcode(**prepared.api_managed_registration)
# Store result.verifiabl_reference with the payslip; result.barcode.data is a base64 PNG.

The examples use Australian v2 payslips. For New Zealand, use prepareNewZealandV2Payslip (Node), V2Issuance.PrepareNewZealand (.NET), or prepare_new_zealand_v2_payslip (Ruby). Map the printed IRD number to the NZ PII fields. Use paye instead of paygw and set the payslip’s currency, for example NZD. The helper selects nz.payslip.v2 and NZ2. See Payslip data for both schemas. Keep employee PII out of non-PII fields.

Store the returned reference. Keep the API response’s Verifiabl reference with your payslip record. The API-managed request does not accept the reference made during preparation. Use the returned value for reconciliation, support, and audit. Verifiabl stores the non-PII data and the encryption metadata. It does not store the encrypted PII.

The API returns the QR code as a base64 PNG in barcode.data. Decode the PNG and put it on the payslip PDF, usually in the bottom-right corner. The QR code spans the full badge width, so the badge does not include its own quiet zone on the left, right or bottom. Keep a clear light margin of at least one tenth of the badge width on those three sides. Do not place it flush against a page edge, border, text or dark content. Make the QR code sufficiently large: a scanner must read it after you print the payslip.

Write the payload into the XMP metadata of the PDF. Then a verifier can read the payload if a re-render or a flatten operation removes the QR code. The metadata holds the pipe-delimited payload 2|<verifiablReference>|<BASE32>, not the scan URL.

import { buildBarcodePayload } from "@verifiabl/issuer";

const xmpPayload = buildBarcodePayload(prepared.barcodeParts(result.verifiablReference));
string xmpPayload = VerifiablBarcode.BuildPayload(
    prepared.BarcodeParts(result.VerifiablReference));
xmp_payload = Verifiabl::Issuer.build_barcode_payload(
  **prepared.barcode_parts(result.verifiabl_reference)
)

The value is the encrypted payload. It does not contain plaintext PII. Refer to PDF metadata copy for the XMP namespace, the property name, and the SDK constants for the two values.

These steps are the full API path. See registerAndBuildBarcode for the request fields, response fields, status codes, and cURL example.