Skip to content

Use case

Waiting on other systems

A partner, a lab or a carrier settles the row by the key it already knows. One statement, one resolution, and a deadline for the answer that never comes.

Surfaces: HTTP · SDK · CLI

The moment

Order A-1042 is packed and labeled. The workflow cannot close it until the carrier confirms pickup. The carrier will confirm, eventually, by calling a webhook with its own payload and the one identifier both sides share: the order number.

The same shape shows up everywhere. A lab returns a test result for a sample. A partner approves a quote. A payment processor settles a charge. Each one answers on its own schedule, knows only its own ids, and never learns yours.

The usual glue is a mapping table from their key to your record, a handler that looks the record up and then updates it, and a nightly job that finds the waits nobody answered. The lookup and the update are two steps, so two webhooks for the same order can race between them.

The intent

The workflow parks a wait that carries the shared key as a facet:

workflows/ship.ts
import { Durable } from '@hotmeshio/hotmesh';
import { conditional, type LTEnvelope } from '@hotmeshio/long-tail';
type PickupV1 = { carrierStatus: 'picked-up' | 'refused'; scannedAt: string };
export async function shipOrder(envelope: LTEnvelope) {
const { orderId } = envelope.data;
const pickup = await conditional<PickupV1>(`carrier-pickup-${orderId}`, {
role: 'carrier-updates',
type: 'shipping',
subtype: 'awaiting-pickup',
description: `Waiting for carrier pickup of ${orderId}`,
metadata: { orderId },
timeout: '48h',
});
if (pickup === false) return await chaseCarrier(orderId);
if (pickup === null) return { data: { orderId, cancelled: true } };
return { data: { orderId, carrierStatus: pickup.carrierStatus, at: pickup.scannedAt } };
}

Two things make this row easy to find from the outside. The orderId facet sits in GIN-indexed metadata. The signal id, carrier-pickup-A-1042, is deterministic, so a caller that knows the order can name the wait without asking for it first.

Three ways in

You know Call Use it when
The order id resolveByMetadata · POST /api/escalations/resolve-by-metadata A partner reports on its own key
The signal key resolveBySignalKey · POST /api/escalations/resolve-by-signal-key Your own handler builds the key from the event
The escalation id resolve · POST /api/escalations/:id/resolve You stored the id when the row was created

resolveByMetadata finds the highest-priority pending row with that key and resolves it in one statement. There is no read-then-write in between, so there is nothing to race.

The webhook handler

routes/carrier-webhook.ts
import { createClient } from '@hotmeshio/long-tail';
const lt = createClient({ auth: { userId: carrierServiceAccountId } });
app.post('/hooks/carrier', async (req, res) => {
const { reference, status, scannedAt } = req.body;
const result = await lt.escalations.resolveByMetadata({
key: 'orderId',
value: reference,
resolverPayload: { carrierStatus: status, scannedAt },
metadata: { carrier: 'acme-freight', carrierStatus: status },
});
if (result.status === 404) return res.status(202).send('no pending pickup for this order');
return res.sendStatus(200);
});

From another language, the same call is one request. Authenticate with a service account key as a bearer token:

Terminal window
curl -X POST https://lt.acme.dev/api/escalations/resolve-by-metadata \
-H "Authorization: Bearer $LT_CARRIER_KEY" -H 'Content-Type: application/json' \
-d '{ "key": "orderId", "value": "A-1042",
"resolverPayload": { "carrierStatus": "picked-up", "scannedAt": "2026-10-20T14:02:00Z" },
"metadata": { "carrier": "acme-freight" } }'

The resolverPayload resumes the workflow. The metadata patch is merged into the row’s indexed metadata in the same statement, so the row records what was asked and what happened, side by side. Six months later, “every order acme-freight refused in September” is a facet query, with no side table to reconcile.

Who may answer

The carrier connects as a service account with one role, carrier-updates. It can resolve rows on that role and nothing else, and revoking its key ends its access in one place. A completing resolve records the completer in the row’s resolved_by, so the row says the carrier answered.

System ingress ignores claims

At 9 a.m. a support agent claims the A-1042 row to chase the carrier by phone. At 9:05 the carrier’s webhook arrives. It lands anyway.

A claim is a work lock between people. resolveBySignalKey and resolveByMetadata are system ingress: they resolve on behalf of the process, not a claimant, so they ignore claims. A person’s resolve by id still respects the lock. Once the carrier’s answer lands, the support agent’s later submit finds the row already resolved.

Early answers wait for the question

Sometimes the answer is faster than the question. A signal that races ahead of the conditional registration is held as pending and delivered when the wait registers, for ten minutes by default.

The answer that never comes

After 48 hours with no pickup, the timer wins. The wait returns false, the row is marked expired in the same statement, and a late webhook finds nothing pending to resolve. It cannot resume a workflow that has moved on. The workflow decides what silence means. Here, it asks a person:

async function chaseCarrier(orderId: string) {
const call = await conditional<{ rebooked: boolean; note?: string }>(`carrier-chase-${orderId}`, {
role: 'shipping-desk',
priority: 1,
description: `No carrier pickup for ${orderId} after 48 hours. Call the carrier and rebook or cancel.`,
metadata: { orderId, reason: 'pickup-overdue' },
timeout: '24h',
});
return { data: { orderId, rebooked: !!call && call.rebooked } };
}

The overdue shipment is now a row in a person’s queue, with the order id, the deadline and the history attached.

Operators from the terminal

When the carrier’s feed is down and the dock calls in a pickup, an operator settles it by the same key:

Terminal window
ltc esc find-by-meta orderId A-1042 --status pending
ltc esc resolve-by-meta orderId A-1042 --data '{"carrierStatus": "picked-up", "scannedAt": "2026-10-20T14:02:00Z"}'

The CLI goes through the same endpoint, the same role checks and the same single statement.