Live inventory checks
Let Localoy ask your own system whether an item is available before it accepts a booking.
With inventory checks switched on, Localoy asks your system before it accepts a booking for a linked item — so a table, ticket or session that sold out in your own system cannot be double-booked on Localoy.
When Localoy calls you#
Localoy calls your inventory endpoint when all three hold:
- A customer is booking an event ticket, an activity or a dining reservation.
- That bookable is linked to one of your catalogue items through
localoyRef. See Link an item to a Localoy listing. - Your production inventory endpoint is saved and enabled in the Partner Portal.
The call is made before the booking is written, and the customer waits for your answer.
- Customer → Localoy: Book sku-4471 × 2
- Localoy → Your inventory endpoint: GET ?external_id=sku-4471&quantity=2
- Your inventory endpoint → Your inventory endpoint: Verify signature, count stock
- — If available —
- Your inventory endpoint → Localoy: 200 { available: true }
- Localoy → Customer: Booking created
- — If sold out —
- Your inventory endpoint → Localoy: 200 { available: false, remaining: 1 }
- Localoy → Customer: Refused: only 1 left
- — If no valid answer within 1.5 s —
- Localoy → Customer: Refused: try again in a moment
Set up the endpoint#
In the Partner Portal, open Open Network → Inventory and save an endpoint URL for each environment.
It must be https and reachable from the internet. Each environment has its own signing secret,
starting invsec_, which you can reveal and rotate there. Rotation takes effect immediately.
The request#
GET /localoy/inventory?external_id=sku-4471&quantity=2&at=2026-09-30T13%3A30%3A00.000Z&module=dining HTTP/1.1
Host: pos.example.com
Accept: application/json
User-Agent: Localoy-Inventory/1.0
X-Localoy-Signature: t=1790410530,v1=5b1f0c…
X-Localoy-Request-Id: 9b2d6f1e-3c7a-4e8b-a5d0-1f2e3c4b5a69| Query parameter | Always sent | Meaning |
|---|---|---|
external_id | Yes | The externalId of your catalogue item. |
quantity | Yes | How many are being booked — seats, people or units. |
at | No | When the booking is for, in ISO 8601 UTC. Sent only when the booking has a date or time. |
module | Yes | event, activity or dining. |
Query parameters already in your endpoint URL are kept.
Verify the signature#
Inventory checks are signed like webhooks, with one difference: there is no body, so the signed string is the path and query exactly as received:
{t}.{path}?{query} e.g. 1790410530./localoy/inventory?external_id=sku-4471&quantity=2&…v1 is the hex HMAC-SHA256 of that string, keyed with your full invsec_… secret. Use the raw request
target — before any URL decoding — because the query is signed in its encoded form (%3A, not :).
import { createHmac, timingSafeEqual } from "node:crypto";
app.get("/localoy/inventory", (req, res) => {
const header = req.get("X-Localoy-Signature") ?? "";
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return res.sendStatus(401);
// req.originalUrl is the path and query exactly as received
const expected = createHmac("sha256", process.env.LOCALOY_INVENTORY_SECRET)
.update(`${t}.${req.originalUrl}`)
.digest("hex");
const given = Buffer.from(parts.v1 ?? "", "hex");
if (given.length !== 32 || !timingSafeEqual(given, Buffer.from(expected, "hex"))) {
return res.sendStatus(401);
}
const left = stockFor(req.query.external_id, req.query.at);
const wanted = Number(req.query.quantity);
res.status(200).json({ available: left >= wanted, remaining: left });
});Your answer#
Answer 200 OK with a JSON object:
{ "available": true, "remaining": 12 }| Field | Type | Required | Meaning |
|---|---|---|---|
available | boolean | Yes | Whether the requested quantity can be booked. |
remaining | integer or null | No | How many are left. Shown to the customer when you say false. |
- Answer "sold out" as
200with"available": false— not as an error status. - Any status other than
200— including201,204and redirects — counts as a failed check. - The body must be valid JSON of at most 64 KB. Other fields are ignored.
- Answer within 1.5 seconds.
What happens next#
| Your answer | The booking |
|---|---|
available: true | Continues to Localoy's own checks and is created. |
available: false | Is refused. The customer is told the item is sold out, or how many are left. |
| No valid answer — timeout, error status, bad JSON | Is refused, and the customer is asked to try again. |
If your endpoint fails 5 times in a row, Localoy stops calling it for 60 seconds, and bookings of linked items are refused during that time without a call. The next booking after the pause tries your endpoint again.
An unreachable endpoint blocks bookings
Because a failed check refuses the booking, keep the endpoint fast and highly available. If you need to take it down, disable it in the portal first — bookings then go ahead without a check.
Monitor your checks#
The Partner Portal lists your last 50 checks — real bookings and tests — with the answer and how long it took. Use the Test button to send one real, signed check to either environment's endpoint.