# Business Central put item.gtin in the standard API — and forgot the webhook

> transactions.dev · by visibility.cloud — 2026-07-31
> Written for the in-house supply-chain platform team — the platform engineer wiring a mid-market brand's system of record.

One thesis: **Business Central is the easiest genuinely GTIN-bearing ERP
connector in the landscape — the standard v2.0 API carries the full
document cycle and a real `item.gtin` field — and the one thing it forgets,
webhook coverage for purchase documents, is a solved problem the moment the
intake design says plainly which entities push and which get polled.**

## The compile map

Business Central's REST API v2.0 (Entra ID OAuth2) exposes the four
reference types as first-class resources, by these exact names:

- `purchaseOrders` → `po`
- `salesShipments` → `desadv` (the outbound despatch side)
- `purchaseReceipts`, with lines, GET-only → `recadv`
- `purchaseInvoices` / `salesInvoices` → `inv`

That is the full `po` → `desadv` → `recadv` → `inv` cycle as API objects —
a property shared by exactly three ERPs in our verified matrix
([NetSuite is the mid-market one](/blog/netsuite-has-the-full-cycle/)), and by
[almost nothing else anywhere](/blog/where-the-document-cycle-lives/). The
compile lands each object as a CBV business-transaction reference on the
events it contextualizes:
[core fields, not extensions](/blog/biztransaction-is-a-core-field/), and
the output validates against the pinned official EPCIS 2.0 JSON schema —
superset where BC's records carry more than the standard models, and
conformant always.

## Why `item.gtin` matters more than it sounds

The field is easy to skim past in the resource docs and it is the whole
ballgame: **`item.gtin` is in the standard v2.0 API payload.** Not a
partner extension, not a per-tenant custom field, not a free-text barcode
column — a named GTIN property on the item resource.

Here is what that buys, concretely. The join that attaches a PO line or a
receipt line to captured events runs on GTIN: the class identifier inside
`urn:epc:class:lgtin:` and `urn:epc:id:sgtin:` URIs. When the source
system names the GTIN itself, the join is a checksum-validated equality
test. When it doesn't — and
[most of the API economy doesn't](/blog/the-gtin-gap/) — the join runs
through a maintained SKU↔GTIN crosswalk with an honesty grade attached.
BC connectors start at the strong end of that gradient on day one, which
is why the matrix scores it the easiest *genuinely GTIN-bearing* ERP:
self-serve trial tenant, sandbox environments, even a Docker image for
local development. No other ERP in the matrix lets a platform engineer
stand up a real instance before lunch.

One flag, stated as a flag and not inferred past: **GLN fields exist on
Customer and Vendor in the base application** (they are there for
e-document and EDI use) **but are not exposed in the standard API
payload.** The compile therefore takes party identity from the
customer-supplied party master rather than pretending the API delivers
it — the same resolve-don't-demand posture the
[enrichment layer](https://barcoding.dev) applies everywhere identity is
incomplete.

## The webhook gap, and the intake design that respects it

Now the forgetting. BC has native webhooks — `subscriptions` with a
three-day renewal cycle — but the supported entity list covers
`purchaseInvoices`, `salesOrders`, `items`, `vendors`... and **not
`purchaseOrders`, and not `purchaseReceipts`.** The two purchase-side
documents that open and close the receiving story are exactly the two the
eventing surface skips.

The wrong responses to that are (a) pretending it isn't true and shipping
an intake that silently misses receipts, or (b) polling everything on a
timer and calling it architecture. The connector's intake design is a
per-entity table, and the table is the contract:

- `purchaseInvoices` — webhook (subscription, renewed inside the 3-day
  window)
- `salesOrders`, `items`, `vendors` — webhook
- `purchaseOrders` — polled, delta-tracked on the modification timestamp
- `purchaseReceipts` — polled, delta-tracked; GET-only is fine because the
  compile never writes back
- `salesShipments` — polled

Webhook-where-possible, poll-where-not, **stated per entity** — so the
latency characteristics of each reference type are a documented property
of the record, not a surprise found during an incident. A `recadv`
reference sourced from a polled entity carries the poll interval as its
worst-case staleness; the [reconcile verdict](/blog/reconcile-is-a-verdict/)
that consumes it inherits a bound it can print.

## The proof artifact: one receipt, one event skeleton

A `purchaseReceipts` line for a lot-tracked item compiles to the
receiving-side skeleton:

```json
{
  "type": "ObjectEvent",
  "action": "OBSERVE",
  "bizStep": "receiving",
  "quantityList": [
    {
      "epcClass": "urn:epc:class:lgtin:0614141.107346.LOT2026B",
      "quantity": 24,
      "uom": "EA"
    }
  ],
  "bizTransactionList": [
    { "type": "recadv", "bizTransaction": "urn:epc:id:gdti:0614141.00001.PR000451" },
    { "type": "po", "bizTransaction": "urn:epc:id:gdti:0614141.00001.PO001204" }
  ]
}
```

The `epcClass` GTIN component came from `item.gtin`, checksum-verified.
The `recadv` and `po` references came from the receipt and the order it
posted against. Nothing in that document required an extension namespace,
and it validates against the pinned schema before it leaves the pipeline —
the acceptance bar every compile in this family clears or exits nonzero
trying.

## Where this sits in your stack

If you are the platform team at a mid-market brand, BC is likely either
your system of record or your finance system's neighbor. The composition
that covers the whole paper trail is BC (or another
[full-cycle ERP](/blog/where-the-document-cycle-lives/)) for the four
documents, your storefront for the
[order-and-fulfillment side](/blog/ecommerce-has-no-invoice-object/), and
your 3PL for [physical receiving](/blog/shipbob-wro-is-a-receiving-advice/) —
three partial sources, one conformant record, queryable at
[epcis.dev](https://epcis.dev/business-transactions-in-epcis/) grain and
answerable at [visibility.cloud](https://visibility.cloud/asn-po-context-in-traces)
grain.

Running Business Central? [Say so here](/get-access/) — the access flow's
system-of-record question exists so the BC branch lands for the people who
answered it.

---
Access: https://transactions.dev/get-access/ · All posts: https://transactions.dev/blog/ · Machine face: https://transactions.dev/llms.txt
