Webhooks
Webhooks let VastVision tell your server the moment something you care about happens, so your application does not have to poll. You decide what should trigger a webhook by building an automation recipe in the VastVision web app. The most common recipes for this integration fire when an item leaves the inventory area (moves from an interior zone to an exit zone) and when it returns.
API calls vs. webhooks
Section titled “API calls vs. webhooks”| Direct API calls | Automation webhooks | |
|---|---|---|
| Direction | Pull: your server asks VastVision. | Push: VastVision POSTs to your server. |
| Timing | Whenever you call. | Within seconds of the automation completing. |
| What you get | Current state (catalog, counts, zones, stock alerts) or a recent window of movements. | Only the movements that match your recipe, with asset, zone, location, and time. |
| Delivery | You control retries and timing. | Each message is delivered once and not retried. |
| Writes | Required for printing and creating assets. | Read-only notifications. |
When to use each
- Use API calls to load state when your application starts, for on-demand views such as the catalog and stock alerts, and for every print or create action.
- Use automation webhooks to react in real time to the movements that matter to your business, such as an item leaving through the Exit.
- Use both together: load state with API calls, keep it current with webhooks, and periodically call
GET /mqtt-change-messages/to backfill anything your endpoint missed (for example, during a deploy).
How automation recipes work
Section titled “How automation recipes work”An automation recipe is a sequence of movement steps followed by an action. Recipes are built on the Automation Recipes page of the VastVision web app, by your administrator or with VastVision’s help. For this integration, we recommend two recipes:
| Recipe | Steps | Action |
|---|---|---|
| Leaves inventory | Asset is in an interior zone, then is read in an exit zone. | Send webhook to your out of inventory endpoint. |
| Returns to inventory | Asset is in an exit zone, then is read in an interior zone. | Send webhook to your back in inventory endpoint. |
You choose where each webhook goes. Every recipe’s webhook action has a Webhook URL field, and VastVision sends the message to exactly the URL entered there. Enter the address of an endpoint on your own server. For example, if your server is your-app.example.com, the two recipes might use:
| Recipe | Webhook URL (example) |
|---|---|
| Leaves inventory | https://your-app.example.com/webhooks/vast/out-of-inventory |
| Returns to inventory | https://your-app.example.com/webhooks/vast/in-inventory |
Giving each recipe its own path makes every message unambiguous: anything posted to /out-of-inventory means an item left, and anything posted to /in-inventory means it came back. Because the signature covers the path, the path cannot be altered in transit. You can change a recipe’s Webhook URL at any time, for example to move from a test endpoint to production.
When you add a webhook action to a recipe, the web app shows a signing secret once. Store it in your secrets manager right away; your endpoint needs it to verify every message.
Testing in your environment
Section titled “Testing in your environment”You do not need to move a real tag to test your endpoint. On the Automation Recipes page, each webhook action shows an example payload and a Send test webhook button.
- In the recipe’s Webhook URL field, enter your development or test endpoint. A temporary HTTPS tunnel to a developer’s machine works well for this.
- Click Send test webhook. VastVision sends a real, signed
POSTto your URL in the same format your production messages will use. - Confirm your endpoint verifies the signature, logs the body, and returns
200. - Compare the logged body with the example payload shown on the page.
A test send looks like this. It has the same structure as a real message, plus "is_test": true at the top level, and uses placeholder values: asset Test Asset, EPC TESTEPC00000001, two steps in Test Zone 1 and Test Zone 2, and the same timestamp on both steps.
{ "is_test": true, "data": [ { "asset": { "name": "Test Asset", "epc_hex": "TESTEPC00000001" }, "zone": { "id": 1, "name": "Test Zone 1" }, "location": { "id": 1, "name": "Test Location" }, "timestamp": "2026-10-28T16:50:36.126482+00:00" }, { "asset": { "name": "Test Asset", "epc_hex": "TESTEPC00000001" }, "zone": { "id": 2, "name": "Test Zone 2" }, "location": { "id": 1, "name": "Test Location" }, "timestamp": "2026-10-28T16:50:36.126482+00:00" } ]}Your endpoint should acknowledge test sends with 200 but not change inventory state.
Payload format
Section titled “Payload format”Every automation webhook is an HTTP POST with a JSON body. data is an array with one entry per recipe step, in order. For the Leaves inventory recipe, the first entry is the interior zone the item was in, and the last entry is the exit zone where it was read.
Using the sample store, when tag A004 moves from the Sales Floor to the Exit, your /out-of-inventory endpoint receives:
{ "data": [ { "asset": { "name": "Peter Millar Polo", "epc_hex": "E2801170000002000000A004" }, "zone": { "id": 12, "name": "Sales Floor" }, "location": { "id": 3, "name": "Downtown Store" }, "timestamp": "2026-10-28T13:47:05+00:00" }, { "asset": { "name": "Peter Millar Polo", "epc_hex": "E2801170000002000000A004" }, "zone": { "id": 14, "name": "Exit" }, "location": { "id": 3, "name": "Downtown Store" }, "timestamp": "2026-10-28T14:52:10+00:00" } ]}| Field | Type | Description |
|---|---|---|
data |
array | One entry per recipe step, in order. |
data[].asset |
{name, epc_hex} |
The asset that moved. Key your records on epc_hex. |
data[].zone |
{id, name} |
The zone the asset was read in for that step. |
data[].location |
{id, name} |
The location the zone belongs to. |
data[].timestamp |
datetime | When that step happened (ISO-8601, UTC). |
is_test |
bool | Present and true only on test sends. |
Handling messages
- The last entry in
datais the asset’s newest position. Use itszoneandtimestamp. - Messages can arrive out of order. Ignore a message whose last
timestampis older than the one you already stored for that EPC. - Use
epc_hexplus the lasttimestampas an idempotency key, so a repeated message has no effect. - Timestamps are ISO-8601 in UTC and may or may not include fractional seconds (e.g.
2026-10-28T14:52:10+00:00or2026-10-28T16:50:36.126482+00:00). Use a standard ISO-8601 parser rather than a fixed format string.