Heatrail

Chapter 02

Advisory by construction

On 1 September 2026 a customer of an Estonian online retailer finished a checkout. Inside that request the shop's server called Heatrail, and the answer came back in about a third of a second. The customer saw nothing of it, which is the whole point. Had the answer taken twice as long, or never arrived, the same confirmation page would have loaded at the same moment and the shop would simply have had no signal for that order. This article is about why that is a fact about the shape of the system rather than a promise about its uptime.

The property

Heatrail cannot stop, delay or cancel a purchase, because the capability does not exist. The response is data. If it does not arrive inside 800 ms, the shop's own client abandons the call and the order proceeds.

Why it holds

A removed capability, not a promise

Heatrail is called once per order, from the shop's server, at the moment the order is about to be handed to the payment provider. It answers with a score and a list of reasons. What it can do with that answer is nothing: there is no callback, no webhook that gates a payment, no configuration flag that turns advisory into enforcing, and no support request that produces one, because there is nothing to switch on.

The reasoning is short. A service that can stop a checkout eventually will, at the worst possible moment, for the wrong reason. Every such service is run by people who intend otherwise, and the intention is not what fails. What fails is a bad deploy on a Friday, a dependency that goes quiet, a rule that was right last quarter, or an operator under pressure from someone with leverage. A promise survives all of those only as long as everyone remembers it. An absent capability survives them without anyone remembering anything.

This is the reason the boundary is stated first in this library rather than last. Everything else Heatrail does is a claim about accuracy, and a claim about accuracy is worth having only from something that cannot act on being wrong.

The budget is on the shop's side of the wire

The 800 ms budget is enforced inside the shop's own process, by the plugin and the SDK, and not by anything we run. Past the budget the client abandons the request, puts the payload on a local retry queue and returns control to the checkout, which then does what it would have done anyway.

The consequence is worth being explicit about, because it inverts the usual vendor relationship. Heatrail being slow is not a problem Heatrail has to solve before the shop is safe. By the time we could notice, the client has already stopped waiting. Our availability is a question about how much signal a shop gets, never a question about whether it can sell.

One detail follows from that. The SDK refuses a zero timeout at construction, because the underlying HTTP client reads zero as no timeout at all, and a shop that configured zero while meaning unlimited patience would have removed the budget without noticing. It is rejected at construction rather than accepted and worked around later. Labelling behaves the opposite way on purpose: a check must never throw, and a label must never be silently lost, so a failed label raises an error the shop's queue can retry.

Figure 1

The call to Heatrail is a side branch off the path from checkout to payment provider, and the branch has a budget.A box labelled Checkout on the left is joined by one solid arrow to a box labelled Payment provider on the right. Above that line sits a box labelled Heatrail, reached by a dashed arrow leaving the line and returning to it. The only number in the drawing sits beside the dashed branch and reads 800 ms. A note below reads that nothing on the solid path waits for the branch.Checkoutorder about to be sentPayment providerorder proceedsHeatrailanswers with data800 msthe budget, held by theshop's own clientthe path that carries the orderNothing on the solid path waits for the branch. Past the budget the branch is abandoned and the order goes on.
The call to Heatrail is a side branch with a budget. The path from the checkout to the payment provider never passes through it.

Degraded is a shape, not an error

When the budget runs out, when the host does not resolve, when the response is not a 2xx or the JSON is malformed, the client returns one fixed thing: ['degraded' => true, 'score' => 0, 'reasons' => []]. The zero is there because there is nothing to report, not because the order was assessed and cleared. Read degraded as no signal for this order. It never means low risk, and it is never an exception the checkout has to catch.

The numbers from the first day in production, 1 September 2026: a median round trip of 386 ms, a 95th percentile of 610 ms, and a slowest check of 799 ms against the 800 ms budget. Even the slowest check of the day came back inside the budget, and a check that had not would have cost that order its signal, not its sale.

The same day we ran the drill in staging: DNS resolution for the Heatrail host was broken deliberately, the purchase completed, and the check returned degraded in 16 ms, then drained from the queue when service returned. Sixteen milliseconds is not a measure of how fast we are. It is the cost of a client deciding, on its own, that it is not going to wait.

The three latency figures are production measurements. The 16 ms figure comes from a staging drill. Availability here decides how much signal a shop receives, never whether its checkout works.

Figure 2

Two lanes, one with Heatrail answering and one with Heatrail unreachable, both ending at the payment provider.The upper lane shows a box labelled Checkout joined by a solid arrow to a box labelled Payment provider, with a dashed branch down to a box labelled Heatrail and back, marked 386 ms. The lower lane is identical, except the dashed branch is crossed by two short strokes before it reaches the Heatrail box, which is drawn with a broken outline, and the branch is marked degraded, 16 ms. Both lanes reach the same payment provider box in the same way.Lane 1, an ordinary checkCheckoutorder readyPayment providerorder proceedsHeatrailscore and reasons386 msmedian, first live dayLane 2, the same shop with Heatrail unreachableCheckoutorder readyPayment providerorder proceedsHeatrailnever reacheddegraded, 16 msthe shape the client returnsto the checkout by itselfBoth lanes end in the same place. The only difference is that lane 2 carries no signal for that order.
Measured on the first live day and in the same day's outage drill. Both lanes end in the same place, and only one of them carries a signal.

Verify it

Read the client rather than this page. The timeout is a constant in the public SDK and the degraded response is a fixed array, so both claims are two greps away from being settled in your own checkout.

grep -n "DEFAULT_TIMEOUT_MS" src/HeatrailClient.php
grep -n "'degraded' => true" src/HeatrailClient.php

Then run the drill we ran. Point the SDK at a hostname that does not resolve and complete a test purchase. The order completes, the check returns degraded, and the payload waits in the local queue. If any part of that does not happen in your integration, it is a bug and we want to hear about it.

Heatrail

This site is not public yet

Heatrail is in a private pre-launch period. Enter the access password to continue.

Enquiries: hello@heatrail.eu