You add a stamp. On an Android phone, the counter on the card has already changed. On an iPhone, it will change the moment the customer opens their wallet, not the second you scan.
No stamp is lost: it is recorded on the server before the card knows anything about it, and the card shows the current state as soon as it asks for it. What differs is the trigger.
This article explains why, because the reason is interesting, and because a product that lets people believe in the instant ends up accused of lying rather than of having a limit.
The two wallets do not update the same way
On the Google side, updating a card is an ordinary web call. Kanz calls, the object changes, that is the end of it.
On the Apple side, the card on the phone does not go and fetch its new state by itself. The server is supposed to send a small silent notification telling the iPhone "your card has changed, come and get it". It is not a visible notification: it is a wake-up, with an empty payload, and it is the phone that then comes for the new version.
That wake-up goes through Apple's notification service, which speaks only HTTP/2 and refuses HTTP/1.1.
Where Kanz runs, that protocol is not available
Kanz runs on an edge runtime, the one that makes the scan fast everywhere. That runtime cannot open an HTTP/2 connection to an arbitrary server. Three ways of doing it were tried, and here is what each one answers:
http2.connect(...) → the method is not implemented
tls.connect({ ALPNProtocols: ['h2'] }) → the option is not implemented
fetch('https://api.push.apple.com/…') → network connection lost
A control call to any other site from the same isolate answers 200. So this is not a network block and not a firewall story: it really is the protocol.
That also rules out the two usual reflexes. Rewriting the module with fetch does not work;
that was the first hypothesis and the measurement killed it. Moving the code to the other
service does not work either, because it is the same kind of runtime.
What it actually costs
Only immediacy, and only on iPhone.
The card's internal counter goes up with every stamp, so the moment a device comes to look, it gets the current state. The customer opens their wallet at the counter to present the card, and that gesture is precisely what triggers the update. In the main case, the next visit, the card is therefore up to date at the exact moment somebody looks at it.
What is lost is the small pleasure of watching the counter move in front of the customer while they hold their phone. That is real and it is not nothing, but it affects neither the counting, nor the reward, nor the reliability.
What it would take to bring it back
One small ordinary service, hosted anywhere, exposing a protected entry point that Kanz would call to have the wake-up relayed. Everything else is already written and correct: the signing of the authentication token, the topic and priority rules, and the handling of device tokens that have stopped answering.
Put another way, this is not a design problem, it is one more piece of infrastructure. It has not been added because an extra moving part, one that can fail on its own, to gain a few seconds on a counter the customer is going to look at anyway, is not a good trade today.
Three details that make this fail silently
For anyone building the same thing, here are the traps. Each one produces a success response from Apple's server and a card that does not move, which is the worst possible combination to debug.
The topic of the notification has to be the pass type identifier, not an application identifier. The payload has to be an empty object: this is a wake-up, not a notification. And a background notification requires low priority; at high priority it is accepted and then ignored.
Why this is written down
Because a user who notices the lag deserves better than "it should update on its own". And because a product that documents its limits where they can be read is easier to believe when it asserts the rest.
How the card works in general, from the first scan to the reward, is set out here.