Build1 publisher3 min readPublished
Temperô's iFood integration discarded every real polled event for weeks by switching on the short code
Temperô's iFood integration checked a field holding 'PLC' against cases written for 'PLACED', so every real event hit a silent no-op for weeks. On a poll-and-acknowledge queue, a default branch that logs nothing lets events disappear without an error.
The Engineer · Build desk

What happened
- iFood's Order API delivers events to restaurant systems through a polling endpoint, and a separate acknowledgment call removes each event from the queue.
- For weeks, Temperô's event switch compared event.code against full names like PLACED, so every real event fell into a default branch that did nothing and logged nothing.
- The fix was a one-field change: the switch now reads event.fullCode instead of event.code.
- The author names intermittently missing cancellation-confirmation events as the longest investigation of the whole project.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- exposure Because an acknowledged event leaves iFood's queue, a consumer that acknowledges before its handler recognises the code turns a dispatch typo into events iFood cannot hand back.
- decision Teams integrating poll-and-acknowledge APIs have to test dispatch against captured real payloads, since curl checks of neighbouring code kept passing while every real event was dropped.
- constraint The fullCode fix explains a total outage, so it cannot be read as the answer to the intermittent cancellation losses that took the team longest to chase.
Temperô, a multi-tenant restaurant-management SaaS built on Node, TypeScript and Postgres [1], gets two similar fields on every polled iFood event. One is `code`, an abbreviation such as "PLC". The other is `fullCode`, the long name such as "PLACED" [8]. The manual curl tests kept passing because they exercised other parts of the flow, not event processing itself [10]. They were correct about everything they tested [10]. The team found the mismatch by pulling the raw JSON of one captured event from its structured logs and comparing it, field by field, with iFood's documentation [11].
The queue contract decides whether a bug like this loses data or only delays it. The two calls are `GET /order/v1.0/events:polling` and `POST /order/v1.0/events/acknowledgment` [2]. There is no push path, because iFood never calls Temperô [4]. A consumer that acknowledges everything it fetched, whatever its handler did, turns an unmatched code into an event iFood no longer holds [2]. The write-up does not say whether Temperô acknowledged the events that fell through. If it did, polling again could not have brought them back.
The rest of the loop is careful work. Every 15 seconds a `setInterval` callback sets a `polling` flag, awaits the poll and clears the flag in a `finally`, so a slow tick cannot start a second run on top of itself [3]. The flag lives in process memory and the poller has no separate service, so the guard covers one Node instance [3]. Tokens renew 30 seconds before the `expiresIn` that iFood returns, and any 401 invalidates the token and retries once [6]. Choosing iFood's Centralized credential forced a mid-project refactor [5]. One `client_credentials` token serves the whole platform, and each request picks its merchant with the `x-polling-merchants` header [5]. The per-unit token cache became a single in-memory cache [5].
In my view the default branch needs a log line and a counter. Logging the unrecognised code with the event id would have flagged the first real order. I would also send the acknowledgment only after a handler succeeds, so an unknown code stays pending on iFood's side until someone reads it.
The cancellation problem had a different shape. By the author's account it was intermittent [13], while the code bug dropped every real event [9], so the fullCode change cannot account for it. Temperô also starts cancellations itself [7]. Its v1 handles delivery only, and takeout or dine-in orders arriving through the marketplace are refused with `requestCancellation` and the first reason code on the list [7]. Any confirmation of those has to come back through the same polling queue [4]. The source text ends before it explains what that investigation found [13].
What to watch
- The author's account of the missing cancellation confirmations, and whether the cause sits in Temperô's acknowledgment order or in iFood's event stream.
- Whether Temperô runs its poller on more than one Node instance, since the in-memory overlap flag guards only a single process.