Guides
Embed a tracking widget
Put live tracking on a client's own site, from a zero-config hosted embed to a self-hosted proxy, without a secret ever reaching the browser.
A second, narrower surface sits beside the main API: a public-facing widget your customers can put on their own websites to show live tracking, without you handing them a tmcp_ key. One widget, three ways to embed it: they differ only in who holds the access credential and how much setup the client’s side needs, not in what a visitor sees.
The widget only ever resolves shipments the org has explicitly registered on its allowlist. An unrecognised reference is refused before any lookup runs, at zero cost, so it can never be used as a free public tracker against the account.
Which tier to use
| Tier | How it works | What the client needs | Use it when |
|---|---|---|---|
| A: Hosted embed (default) | One tag pointed at our domain. We render it, secure it, and update it. | Nothing. No server, no key in their HTML. | Almost always. The default unless something below forces another choice. |
| B: Their own proxy | A small Cloudflare Worker template holds the real key as a Worker secret; the browser only ever talks to the client’s own domain. | A developer, ~10 minutes, a free Cloudflare account. | Branding needs to be pixel-matched, or the client wants full control over markup. |
| C: Signed token | A short-lived token with the client’s domain, a rate limit and a 1-hour expiry signed into it (HMAC-SHA256). Still visible in page source, but forged limits are not possible. | Nothing to deploy, but the token must be minted and renewed from somewhere. | Fully static hosting with no server and no Worker option: the one case where A and B genuinely don’t apply. |
Tier B ships as a public template at navo24-proxy-worker; ask your contact for the current status if you need it today.
Before you start
Turn the widget on from Settings → Tracking widget: set at least one authorised domain and switch “Widget enabled” on. An empty domain list refuses every request, it does not mean “allow anywhere.” The same panel generates a ready-to-paste <script> snippet using your embed_key, and lets you register the shipments the widget may show.
Every widget-key request, raw embed_key or signed token, is capped at 120 requests a minute. This is a separate, simpler limiter from the main API: it does not set X-RateLimit-* response headers. Going over the cap returns a bare 429 with { "ok": false, "message": "…" }; back off under a minute and retry.
Tier A: hosted embed
See it live: navo24.com/widgets/ shows the widget across different product themes, and navo24.com/example-site/ is a full working demo site with the widget embedded in a client brand, so you can show a client exactly how it looks wired into a real page before you build anything.
<iframe
src="https://api.trackingmcp.com/v1/track/widget/iframe/YOUR_EMBED_KEY"
style="width:100%;border:0"
title="Track a shipment"
></iframe>
Everything (markup, the allowlist check, the carrier lookup) runs on our servers. There is nothing in the client’s page source that can be copied and reused elsewhere: the embed_key only renders when framed from a domain on the org’s allowlist, enforced by the browser itself through a per-request Content-Security-Policy: frame-ancestors, not just a Referer header we could get wrong.
The first version is a plain server-rendered form: typing a new reference reloads the frame, and there is no automatic height adjustment yet: fix a height on the <iframe> or expect some whitespace.
Tier B: your own proxy
A short Cloudflare Worker template (about 40 lines) holds the embed_key as a Worker secret and forwards only the two public JSON endpoints below to the client’s own domain. The browser never sees trackingmcp.com or the real key, only the client’s worker.
Tier C: signed token
Mint a token server-side, or from a build step, using the org’s dashboard session:
curl -X POST https://api.trackingmcp.com/v1/widget/config/token \
-H "Authorization: Bearer YOUR_DASHBOARD_SESSION"
{ "ok": true, "data": { "token": "wgts_…", "expires_in": 3600 } }
Ship that token to the browser and use it exactly like an embed_key on the two endpoints below. Renew it before it expires (or within 10 minutes after) with no dashboard session needed: this is the call the browser itself makes:
curl -X POST https://api.trackingmcp.com/v1/track/widget/renew \
-H "Authorization: Bearer wgts_…"
{ "ok": true, "token": "wgts_…", "expires_in": 3600 }
A visitor can still read the token out of the page; the signature limits the damage to one domain, one rate limit and one hour, it does not remove the exposure. Treat this as the floor, not a destination. Move to A or B once you can.
Reading shipment data
Both the raw embed_key and a signed wgts_ token work as a bearer credential on these two routes:
| Method | Path | What it returns |
|---|---|---|
GET | /v1/track/widget/tracking/{number} | One shipment, if it is on the org’s allowlist. |
GET | /v1/track/widget/shipments | Every shipment on the allowlist, with its last known status. |
curl https://api.trackingmcp.com/v1/track/widget/tracking/MSCU8142203 \
-H "Authorization: Bearer YOUR_EMBED_KEY_OR_TOKEN"
{
"ok": true,
"number": "MSCU8142203",
"carrier": "Mediterranean Shipping Company",
"carrier_code": "MSCU",
"status": "in_transit",
"eta": "2026-11-02T00:00:00.000Z",
"origin": "Busan, KR",
"destination": "Long Beach, US",
"events": []
}
Every field except number can come back null: nothing here is invented. This is a read of the org’s own already-tracked shipment data, not a live carrier call, so it is never slow and never rate-limited by a carrier on the other end. events is always empty today; a full timeline on this endpoint is on the roadmap, not yet built.
Managing the widget
Dashboard-authenticated (the org’s own tmcp_ key or session), not for use from the public widget itself:
| Method | Path | What it does |
|---|---|---|
GET | /v1/widget/config | The org’s embed_key, authorised domains, accent colour, enabled flag. |
PATCH | /v1/widget/config | Update domains, accent, powered-by, or enabled. Enabling refuses with a 400 until at least one domain is set. |
POST | /v1/widget/config/rotate | Mint a new embed_key, invalidating the old one. |
POST | /v1/widget/config/token | Mint a 1-hour signed token for Tier C. |
GET | /v1/widget/allowlist | The shipments the widget may resolve. |
POST | /v1/widget/allowlist | Register one or many references (container, B/L or booking). |
What’s actually protecting you
Two gates apply to every tier, not just the ones that look more secure at a glance.
Domain lock. The widget cannot be enabled without at least one authorised domain, and every request is checked against it: Origin/Referer on the data endpoints, a browser-enforced Content-Security-Policy on the hosted embed. A key copied out of the page’s HTML does not work from anywhere else.
Allowlist. Even a correctly-domained request only ever resolves a reference the org explicitly registered. Nothing un-added can be looked up through the widget, at any tier: it is refused before any data read happens, at zero cost to the account.