Sending Tracking Back to Your ERP With Webhooks
A practical pattern for posting tracking numbers to ERP orders from signed webhooks: verify, match by reference, write once, acknowledge, and replay.
To send tracking back to your ERP with webhooks, you run a small receiver that accepts the shipping platform's signed event, verifies the signature, finds the order by the reference you set when the label was bought, writes the tracking number to that order, and returns a success response. Anything the receiver misses is recovered by replaying events, not by someone retyping numbers. The receiver is code you own: the shipping platform sends the event, and your side decides what the ERP does with it.
What does the webhook pattern look like end to end?
It is six steps, and each one exists to prevent a specific failure. The order matters more than the language or the hosting you choose.
- Receive the raw request. Keep the body exactly as it arrived. Signature checks are computed over the raw bytes, so parsing and re-serializing the JSON first will break verification.
- Verify the signature. Compute the expected signature with your webhook secret and compare it to the one sent with the request. Reject anything that does not match before reading the contents.
- Check for a duplicate. Look up the event's unique identifier (see /docs#webhooks for what identifies an event) in a table of events you have already processed. If it is there, skip to step 6.
- Look up the order by the reference. The reference you attached to the label (a sales order number, a shipment number, a PO) is the join key. Find exactly one order, or route the event to an exception queue.
- Write the tracking number. Update the order, shipment, or delivery document through the ERP's supported API. Record the event identifier as processed in the same step where possible.
- Acknowledge. Return a 2xx response quickly. A slow or failed response tells the sender the delivery did not land.
For the exact event names, signature scheme, and payload fields GoatLabels uses, read the webhook documentation and do not guess from examples in a blog post. This article describes the pattern only.
Why verify the signature before anything else?
Because a webhook endpoint is a public URL, and an unsigned request to it is just a stranger asking you to change an order. Verification proves the request came from the platform that holds your shared secret and that the body was not altered on the way.
A few habits keep this step reliable:
- Store the webhook secret where your other credentials live, not in the source code, and keep a separate secret for test and live environments.
- Use a constant-time comparison function from your language's standard library when comparing signatures.
- If the platform's signature includes a timestamp, reject requests that are far outside a reasonable window so an old captured request cannot be sent again later. The documentation says whether one is included.
- Log rejected requests with enough detail to investigate, but never log the secret.
A failed verification should return an error status and do nothing else. Do not write a partial record "just in case."
How do you match the event to the right ERP order?
You match on the reference you supplied when the label was created, which is why the reference convention has to be decided before the first label is bought. If the label was purchased with the ERP's own document number as its reference, the lookup is a single query. If the reference was free text typed at a pack station, the lookup is guesswork.
Choose one document type as the anchor and keep to it. Common anchors are the sales order number or the outbound shipment number. The second can be better when one sales order ships in several parts, because each shipment document gets its own tracking.
Plan for these three outcomes on every event:
| Lookup result | What the receiver should do |
|---|---|
| Exactly one open order or shipment | Write the tracking number and acknowledge |
| No match | Acknowledge, store the event in an exception queue, alert a person |
| More than one match | Acknowledge, store in the exception queue, do not write to either |
Acknowledging a "no match" event may look wrong, but the sender cannot fix your data. Asking it to retry an event that will never match only fills your logs. Keep the event, fix the cause, and reprocess it from your own queue.
Multi-carton orders need one more decision. In GoatLabels, one order can produce several labels that all carry the same reference, so the receiver will see several tracking numbers for one ERP document. Either the ERP field holds a list, or you write each carton as its own package line. Overwriting the field with each new event leaves only the last carton's number, which is a mistake customer service may find before you do.
What does a worked example look like?
Here is an example with hypothetical numbers, to show how the pieces behave on an ordinary day. The figures are invented for illustration and are not measurements from any customer.
A distributor ships 180 parcels on a Tuesday against 150 shipment documents. Labels are bought through the API with the shipment number as the reference, for example SHP-104233.
- 176 events arrive, pass verification, match one shipment each, and write a tracking number. Because 30 shipments had more than one carton, those documents end the day with two tracking numbers apiece.
- 3 events arrive twice because the receiver was slow to respond during a deployment. The duplicate check catches all 3 second deliveries and writes nothing.
- 1 event has a reference of SHP-10423, a keying error on a manual label. It lands in the exception queue. A coordinator corrects the reference and reprocesses it.
- 3 events never arrived because the receiver was down for four minutes during the same deployment. At the end of the day, a reconciliation job compares labels bought (180) to events processed (177), finds the gap, and the team replays the 3 missing events.
The end state is 180 labels, 180 tracking numbers posted, and no manual typing except one corrected reference. The count comparison at the end is what turns "we think it all posted" into something you can show an auditor.
What happens when your endpoint is down or an event is missed?
You rely on replay, and you make replay safe by making the receiver idempotent. Replay means asking the platform to send an event again; idempotent means processing the same event twice has the same result as processing it once.
Three practices cover the common failures:
- Store processed event identifiers. This is the duplicate check in step 3. Without it, a replay can create a second package line or trigger a second customer email from the ERP.
- Reconcile on a schedule. Once a day, compare the labels purchased to the tracking numbers posted. Any label without a posted tracking number is a candidate for replay.
- Acknowledge first, work second, if the ERP is slow. If the ERP's API takes several seconds to respond, put the verified event on an internal queue, acknowledge immediately, and let a worker do the write. This keeps a slow ERP from looking like a failed delivery.
Test all of this before go-live. Use test keys, take the receiver offline on purpose, buy a test label, bring the receiver back, and replay. If the tracking number appears once on the right order, the design works. The same key and retry discipline that protects label purchases applies here, and the shipping API overview covers that side.
Which ERPs can receive a webhook directly?
Some can, but you may still want to put a thin receiver in front of the ERP. Odoo documents inbound webhooks as a trigger for automation rules in Studio, and Microsoft documents webhook subscriptions in the Business Central API v2.0. What a given ERP can do with an incoming request, and whether it can verify a third party's signature, varies by product, edition, and version, so confirm against the vendor's current documentation before designing around it.
A separate receiver is the safer default for three reasons. It can verify the signature in ordinary code. It can hold the duplicate table and exception queue without customizing the ERP. And it keeps ERP credentials on your side of the connection, with the receiver calling the ERP's standard API to update the order.
The receiver can be a small serverless function, a route in an existing internal service, or a job on your integration platform. If you are weighing those choices for a specific system, the pages on ERP shipping integration, Dynamics 365 Business Central, and Odoo shipping labels describe the options in more detail.
Where GoatLabels fits
GoatLabels provides the sending half of this pattern. The REST API includes signed, replayable webhooks, live and test keys, an Idempotency-Key for purchases, an OpenAPI 3.1 description, and unlimited calls on every plan, including the free one. Every label carries your reference into a dated ledger entry, which gives your reconciliation job a second list to compare against.
The limits are just as important. GoatLabels has no native ERP connector and does not write tracking into any ERP automatically. There are no SDKs, so the receiver described here is code your team writes and maintains against the documented API. There is no SLA and no phone support; help is by email and the in-app assistant. If you need a vendor to own the write-back into your ERP, that is a different class of product, and B2B shipping software explains where the line sits. If your team is comfortable owning a small receiver, start with the webhook documentation and a test key.