How to Integrate codeREADr with PassNinja

Daniel Baudino

Updated August 12, 2026

TL;DR: Validate PassNinja wallet passes on NFC terminals with codeREADr.
  1. 1. Create a validate Application on the pass template you want to scan.
  2. 2. Create a reader bound to that Application — its token is your codeREADr secret.
  3. 3. Create a Service in codeREADr configured for Postback validation.
  4. 4. Attach the secret, then authorize users and assign reader devices.
  5. 5. Tap a pass to test, then confirm the result mapping before going live.

PassNinja has partnered with codeREADr as its wallet pass NFC validation provider. Once connected, staff tap an Apple Wallet or Google Wallet pass issued by PassNinja against a codeREADr-compatible terminal (built on VTAP or Zebra hardware) and get an instant valid/invalid result — enforced by whatever redemption policy you configure (one-time use, a rescan window, multiple taps). Every tap is a request to PassNinja: we decide accept or reject, and codeREADr shows your staff a color-coded result. This guide walks through both sides of the setup.

How it works

codeREADr's app or terminal reads the pass over NFC, then POSTs the scanned value to a PassNinja endpoint (/v1/integrations/codereadr/postback) along with a shared secret identifying your reader. PassNinja looks up the pass, applies your Application's redemption policy, and responds with XML codeREADr understands:

<?xml version="1.0" encoding="UTF-8"?>
<xml><message>
  <status>1</status>              <!-- 1 valid · 0 invalid · -1 connection error -->
  <text>Accepted</text>
  <scan_display_config>{"status_title_default":"Valid","status_color_default":"#1f9d55"}</scan_display_config>
</message></xml>
Scenario<status>Result shown
Valid pass, first scan1Green — "Valid"
Same pass again (inside the rescan window)0Red — "Already used"
Pass not installed, expired, or inactive0Red — "Not valid"
Value doesn't match any pass0Red — "Unknown pass"
Bad or missing secret0Red — "Not authorized"

How to create a validate Application

An Application defines what happens when a reader scans a pass on a given template. For NFC validation, you want kind=validate.

From the dashboard:

  1. Log in to your PassNinja dashboard and go to Applications in the left nav.

Note: The Applications and Readers nav items only appear if your account has the scan-events-system entitlement. If you don't see them, reach out to PassNinja to enable it on your plan.

  1. Click New Application.
  2. Set Kind to validate, choose the pass template you want to scan, and set a rescan window — how long before the same pass can scan again. Use a large value (e.g. 86400 seconds / 1 day) for true one-time-use tickets.
  3. Click Create.

Application form with the Kind dropdown open showing log, validate, and forward — validate selected — plus the bound pass template and rescan window fields

From the CLI, if you have the passninja CLI installed and authenticated (passninja auth):

passninja application create \
  --name "codeREADr gate" \
  --kind validate \
  --pass-template ptk_0x... \
  --rescan-window 86400

This prints the new Application's ID (app_0x...) — you'll need it for the next step.

Note: A template can only be paired with a reader while it has zero live issued passes. If your template already has passes issued on it, this step happens before you issue any, or the pairing will be rejected with TEMPLATE_HAS_ISSUED_PASSES.

How to create a reader and get your codeREADr secret

A reader's bearer token doubles as the codeREADr secret — creating a reader here is the only step left before you configure codeREADr.

From the dashboard:

  1. Go to Readers in the left nav and click Add a reader.
  2. Fill in a Name and Location, then check the Application you created in the previous step.
  3. Click Add reader.
  4. Copy the token shown on the confirmation screen — it starts with rdr_... and is shown exactly once. Store it somewhere secure (a password manager, not chat history).

From the CLI:

passninja reader create \
  --name "Front gate" \
  --location "Main entrance" \
  --application app_0x...

The response includes the token (rdr_...) exactly once — save it immediately, it can't be retrieved again (rotate it with passninja reader rotate-token <reader_id> if you lose it).

The Postback URL is always https://api.passninja.com/v1/integrations/codereadr/postback — both values go straight into the codeREADr Service you'll create next.

How to create the codeREADr Service

This part happens entirely in codeREADr's own dashboard — see their guides on choosing a service type and postback scans for the full walkthrough with their own screenshots. The short version:

  1. Log in to your codeREADr dashboard.
  2. Click Services in the left nav, then New Service.
  3. Choose Scan to a Third-Party Server.
  4. Fill in the Service configuration:
SettingValueWhy
Validation methodpostbackSends each scan to PassNinja for validation
Postback URLthe URL PassNinja sent youWhere scans are POSTed
Postback receiver onlyNo (0)codeREADr must receive and honor our valid/invalid answer
Postback real-time scansYes (1)Validates live, not in a later batch
Direct scan (DSU)On for app-to-PassNinja direct, Off for codeREADr to relay + keep its own logPick based on whether you want a scan log on codeREADr's side too — see their DSU guide

Receiver-only and real-time-scans live under the Service's Advanced Settings.

  1. Save the Service.

How to attach the secret

  1. On the Service you just created, go to Scan Properties.
  2. Add a property named secret with the rdr_... token from the reader you just created.

codeREADr sends this property on every scan. It's how PassNinja knows which reader made the tap, and therefore which Application — and which redemption policy — to apply.

For how scan_properties are actually passed through on a scan, see codeREADr's Postback URL and DSU Variables guide.

How to authorize users and assign devices

Also entirely on codeREADr's side — their add a user and device requirements guides cover this in full:

  1. Under the Service, go to Users and authorize the app user(s) who'll be scanning.
  2. Go to Devices and assign the reader device(s) running the codeREADr app.
  3. Configure the symbology / decoder settings for the certified VAS or Smart Tap NFC read (this depends on your terminal hardware — VTAP and Zebra both publish their own decoder reference).

How to test before going live

You don't need a physical device to confirm the PassNinja side is wired correctly — simulate exactly what codeREADr sends with curl:

curl -s -X POST "https://api.passninja.com/v1/integrations/codereadr/postback" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data "secret=rdr_your_secret&value=your_pass_serial"

A valid, unscanned pass returns:

<?xml version="1.0" encoding="UTF-8"?><xml><message><status>1</status><text>Accepted</text><scan_display_config>{"status_title_default":"Valid","status_color_default":"#1f9d55"}</scan_display_config></message></xml>

Run the same request again and you should get Already used instead — confirming the rescan window is enforcing one-time use.

Once that checks out, do one real tap on the terminal and confirm the green/red result on screen matches.

How to check account prerequisites

  • Paid plan on codeREADr — API testing works on their free tier; production traffic needs a paid plan.
  • IP allowlists — exchange IP ranges with PassNinja if either side restricts inbound traffic: codeREADr's postback egress IPs (Account → Postback Integrations page in their dashboard) and PassNinja's outbound IPs if you call their API directly.
  • Certified reader hardware — a VAS/Smart Tap-certified NFC terminal (VTAP or Zebra). This is a hardware requirement from Apple/Google, not a codeREADr or PassNinja gate.

Conclusion

Once connected, every tap on a codeREADr terminal becomes a real-time decision made by PassNinja: valid, already used, or not valid, enforced by whichever redemption policy you set on the Application. The setup lives entirely in configuration — a validate Application on your template, a Postback-mode Service in codeREADr, and the shared secret between them — so there's no code to write on either side. Test with curl before your first live event, and confirm the rescan window matches how you actually want reuse handled (a single gate scan vs. a loyalty card tapped every visit).

Was this article helpful?
Yes No