Guides
Retries and idempotency
Send a request again safely when you don't know whether the first one worked.
Networks fail. A request can time out after we've made the pass but before the answer reaches you, and then you can't tell whether to send it again: once too few and a holder has no pass, once too many and they have two. An Idempotency-Key makes the retry safe.
Send a key with every write
Make a key for each thing you mean to do, such as order-1042 for the pass for order 1042, and send it in the Idempotency-Key header. Send the same key when you retry:
curl "https://api.walletpassapi.com/v1/passes" \
-H "Authorization: Bearer $WALLET_API_KEY" \
-H "Idempotency-Key: order-1042" \
-H "Content-Type: application/json" \
-d '{
"organizationName": "Acme Coffee",
"logoText": "Acme"
}'const res = await fetch("https://api.walletpassapi.com/v1/passes", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WALLET_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "order-1042",
},
body: JSON.stringify({
"organizationName": "Acme Coffee",
"logoText": "Acme"
}),
});
const data = await res.json();import os
import requests
res = requests.post(
"https://api.walletpassapi.com/v1/passes",
headers={"Authorization": f"Bearer {os.environ['WALLET_API_KEY']}", "Idempotency-Key": "order-1042"},
json={
"organizationName": "Acme Coffee",
"logoText": "Acme",
},
)
data = res.json()<?php
$ch = curl_init("https://api.walletpassapi.com/v1/passes");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("WALLET_API_KEY"),
"Content-Type: application/json",
"Idempotency-Key: order-1042",
],
CURLOPT_POSTFIELDS => json_encode([
"organizationName" => "Acme Coffee",
"logoText" => "Acme",
]),
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);The first request with a key does the work and keeps its answer. A request with the same key and the same body after it does nothing again: it gets that first answer back, with the same status. So the retry of a create returns the pass that was made, not a second one; no second operation is counted, and nothing is sent to phones or webhooks twice.
A key is up to 255 characters, and belongs to your workspace: another workspace's keys never clash with yours.
Which requests take one
| Request | Without a key, a repeat… |
|---|---|
| Create a pass | makes a second pass (unless its externalId is taken) |
| Update a pass | changes nothing the second time, so it's already safe |
| Send a notification | shows the message twice |
| Revoke a pass | answers alreadyDeleted: true |
| Delete a revoked pass | answers alreadyErased: true |
| Send a broadcast | sends it again, once the first has finished |
| Upload an image | stores a second copy |
| Create a template | answers template_slug_taken |
| Save, rename or delete a template | changes nothing more, or answers not_found once it's deleted |
| Update a template's passes | answers template_update_in_progress or nothing_to_update |
Reads never need one.
The same key for something else
A key belongs to one request. The same key with a different body, or on a different endpoint, is refused with 409 idempotency_conflict, and nothing is done: it's almost always a bug, such as a key made from something that isn't unique. Use a new key for each new thing.
How long keys are kept
A key's answer is kept for 7 days, far longer than any retry needs. After that, the same key is a new request.
When to retry
Retry when there was no answer at all (a timeout or a dropped connection), a 5xx, or a 429 with code rate_limited (after its Retry-After). Wait longer between each try, such as 1, 2, then 4 seconds. Other errors will fail the same way again: see Errors.