External IDs and idempotency

Why setting extId on every pass is the single most important decision in an API integration - it prevents duplicates on retry and lets you address passes by your own reference.

Updated 7/26/2026

Every pass can carry an external ID - your own reference for it. A customer number, an order id, a booking reference, a ticket number.

Setting one is optional. Doing so is the single highest-value decision in an API integration, and it's worth understanding why before you write the code.

The problem it solves

Network calls fail in an ambiguous way. Your request times out and you don't know whether the pass was created before the connection dropped.

If you retry, you might create a second pass. If you don't, the customer might have none. Neither is acceptable, and you can't tell which situation you're in.

How extId fixes it

Creating a pass with an extId is an upsert. If no pass has that external ID, one is created. If one does, it's updated instead.

That makes creation idempotent: sending the same request twice produces the same single pass. You can retry freely without checking first.

curl -X POST "https://api-prod.getpocketpass.com/api/v1/passes?passTemplate=TPL&extId=order-8891" \
  -H "Authorization: Bearer pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "passContent": { "customerName": { "value": "Jane Doe" } } }'

Run that ten times and you have one pass.

Use the identifier your own system already treats as canonical - the order id, the membership number, the booking reference. Don't mint a new id for PocketPass. The whole point is that both systems already agree on what to call this thing.

The second benefit: addressing by your own reference

Every single-pass route accepts either our UUID or your external ID:

curl https://api-prod.getpocketpass.com/api/v1/passes/order-8891 \
  -H "Authorization: Bearer pk_live_..."

That means your database doesn't have to store our identifier at all. No extra column, no mapping table, no lookup step before every update.

Two rules

External IDs must be unique within your account. They're the key the upsert matches on, so two different things can't share one.

They can't be changed after creation. They're the handle other systems use, so allowing them to move would break exactly what they exist to support.

Because create is an upsert, sending the wrong extId doesn't error - it silently overwrites a different customer's pass, and pushes that change to their phone.

Make sure the value you send is genuinely unique to the thing the pass represents. If your order ids and your membership numbers can collide, prefix them - order-8891, member-8891 - rather than hoping they won't.

Choosing a format

  • Prefix by type when you issue several kinds of pass: order-8891, member-10248, ticket-55.
  • Keep them stable. An id that changes when a record is edited defeats the purpose.
  • Don't use anything sensitive. External IDs can be encoded into the pass barcode - see Barcodes - so never use an email address, a name, or anything you'd rather not have scanned.

What happens without one

Passes without an external ID are perfectly valid, and fine for one-offs and manual issuing.

But over the API you then have to store our UUID to do anything with the pass later, and creation is no longer idempotent - a retry makes a duplicate.

If you're building an integration and thinking "I'll add extIds later", don't. Retrofitting them onto passes already issued means matching records by hand. Two minutes now saves a genuinely unpleasant afternoon.

Retrying safely

Because create is idempotent, error handling stays simple:

  • Timeout or 5xx? Retry the identical request.
  • Network error? Retry.
  • 429? Back off and retry. See API errors and rate limits.
  • 422? Don't retry - the request is wrong, and it'll be wrong again.

You never need a "does this pass already exist?" check. That's the point.

Where to go next

More in Developers