track-shipment▌
dhl.com/track-shipment-18m2jq · updated May 21, 2026
MDX-style export adds YAML metadata + attribution linking explainx.ai and this canonical listing URL.
Track any DHL shipment (Express, Parcel, eCommerce, Global Forwarding) by tracking number and return current status, handler, origin, destination, last-update timestamp, and the full chronological event timeline.
| name | track-shipment |
| title | DHL Shipment Tracking |
| description | >- Track any DHL shipment (Express, Parcel, eCommerce, Global Forwarding) by tracking number and return current status, handler, origin, destination, last-update timestamp, and the full chronological event timeline. |
| website | dhl.com |
| category | logistics |
| tags | - logistics - tracking - shipping - dhl - parcel - akamai |
| source | 'browserbase: agent-runtime 2026-05-20' |
| updated | '2026-05-20' |
| recommended_method | browser |
| alternative_methods | - method: api rationale: >- DHL's internal unified tracking endpoint `https://www.dhl.com/utapi?trackingNumber=...` is confirmed Akamai-blocked (HTTP 428 sec-cp-challenge crypto proof-of-work, verified across 3 fetches from a warmed page context). DHL's public `developer.dhl.com` Tracking API requires registration + API key, so it's out of scope for unauthenticated agent flows. Browser-driven DOM scrape is the only practical surface. |
| verified | true |
| proxies | true |
DHL Shipment Tracking
Purpose
Given a DHL tracking number (DHL Express, DHL Parcel, DHL eCommerce, DHL Global Forwarding, etc.), return the shipment's current status, service / product name, origin, destination, last-update timestamp + location, and the full event timeline (chronological list of every scan / status change with date, local-time, status text, and location). Read-only — never click "Subscribe to notifications", "Schedule delivery", "Redirect package", or any login / account button.
When to Use
- A user pastes a DHL tracking number and asks "where is my package?" / "has it been delivered?" / "when will it arrive?".
- A logistics or customer-support agent monitoring a shipment for a delivery event.
- Bulk status polling for a list of DHL waybills (e.g., warehouse outbound reconciliation).
- Any flow where you'd otherwise tell the user to "go check the DHL site" — do it for them.
Workflow
DHL's public web tracker (dhl.com/<country>/home/tracking.html) is the only practical surface. The underlying JSON endpoint https://www.dhl.com/utapi?trackingNumber=... is confirmed Akamai-blocked for any non-page-rendered request (always returns HTTP 428 sec-cp-challenge Crypto-Challenge, including from page-context fetch() after the page itself has loaded successfully — see Site-Specific Gotchas). DHL's official Tracking API at developer.dhl.com requires an API-key registration and is out of scope for unauthenticated browser-agent flows. Use the browser path; lead with the deep-link URL pattern below.
-
Open a Browserbase session with both stealth + residential proxies enabled — these are mandatory. Akamai's bot-protection (
sec-cpt-ifandsec-text-ifchallenge iframes) is silently embedded on every page load and the verified-fingerprint browser solves the proof-of-work in the background. A bare session fails to render any tracking data and theutapicall never resolves.sid=$(browse cloud sessions create --keep-alive --verified --proxies \ | node -e "let s='';process.stdin.on('data',c=>s+=c).on('end',()=>process.stdout.write(JSON.parse(s).id))") export BROWSE_SESSION="$sid" -
Navigate directly to the deep-link URL — skip the homepage form, type-in-and-submit flow entirely. The query-string
tracking-id=<NUMBER>&submit=1auto-submits and renders results.browse open "https://www.dhl.com/us-en/home/tracking.html?tracking-id=<NUMBER>&submit=1" --remoteThe
/global-en/locale prefix 302-redirects to a country-specific path (e.g./us-en/). Going directly to/us-en/(or any other locale that's stable for your outbound IP) saves one redirect. Localization does NOT change which shipments are visible — DHL's tracking is global; only the page chrome + currency change per locale. -
Wait for the result-injection chain to complete. After
load, DHL's page makes a request tohttps://www.dhl.com/utapi?..., receives a metadata response, then loads a per-carrier HTML snippet from/<country>/home/tracking/tracking-content-injection/<carrier>/<status>.htmland injects it into the DOM. The full chain settles ~3–5s afterload.browse wait timeout 5000 --remote -
Detect outcome before extracting by reading the visible page text. Three top-level branches:
Branch Page-text signature Success Tracking Code: <NUMBER>appears with a status word (e.g.Delivered,In transit,Out for delivery,Arrived at,Picked up) andLast Update: <day>, <date> at <time> Local time, <country>Not found Sorry, your tracking attempt was not successful. Please check your tracking number.(the tracking number is echoed back digit-spaced and unspaced)Anti-bot wall Page body contains Access Denied, an unfilled Akamai challenge persists (document.querySelector('#sec-cpt-if')visible AND no[role="tabpanel"]in DOM afterwait timeout 8000), ordocument.titleis "Pardon Our Interruption" / blank -
On success, extract structured fields from the rendered DOM, not from
utapi:browse eval --remote -- '(() => { const main = document.querySelector("main, .c-tracking--container"); const txt = main?.innerText || ""; const detailsEl = document.getElementById("panel_details"); const eventsEl = document.getElementById("panel_events"); // Header line — example: // "Delivered , Tracking Code: 1 2 3 4 5 6 7 8 9 0 // Last Update: Wednesday, April 29, 2026 at 6:25 PM Local time , France // Origin: Córdoba // Destination: France, France" const status = txt.match(/\n([A-Z][a-zA-Z ]+)\n,\s*Tracking Code:/)?.[1]?.trim() || null; const lastUpdate = txt.match(/Last Update:\s*([^\n]+)/)?.[1]?.trim() || null; const origin = txt.match(/Origin:\s*([^\n]+)/)?.[1]?.trim() || null; const destination = txt.match(/Destination:\s*([^\n]+)/)?.[1]?.trim() || null; const handler = txt.match(/handled by:\s*([^\n]+)/)?.[1]?.trim() || null; // Details panel — labels are stable: Total Pieces, Service, Weight, Reference, Local Tracking Number const detailsText = (detailsEl?.innerText || "").replace(/\s+/g, " "); const grab = (label) => { const m = detailsText.match(new RegExp(label + "\\s+([^\\s][^A-Z]*?)(?:\\s+[A-Z][a-z]+ [A-Z]|$)")); return m?.[1]?.trim() || null; }; // Event log — panel_events renders as a Time / Status / Location table. // Each row = 3 line-pairs: "<Month Day, Year>\n<HH:MM (AM|PM) Local time>\n<Status>\n<Location>" const eventsRaw = (eventsEl?.innerText || "").replace(/^\s*Time\s+Status Update\s+Location/, ""); const eventLines = eventsRaw.split(/\n/).map(l => l.trim()).filter(Boolean); const events = []; for (let i = 0; i + 3 < eventLines.length; ) { const dateLine = eventLines[i]; const timeLine = eventLines[i+1]; const statusLine = eventLines[i+2]; const locLine = eventLines[i+3]; if (/\d{4}/.test(dateLine) && /Local time/.test(timeLine)) { events.push({ date: dateLine, time: timeLine, description: statusLine, location: locLine }); i += 4; } else { i += 1; } } return { status, lastUpdate, origin, destination, handler, eventsCount: events.length, events }; })()'The three tabpanels in the page are all rendered non-hidden simultaneously — DHL's UI uses CSS visibility, not
hidden=true, to switch tabs. This means you do NOT have to click "Event Log" to access its content; thepanel_eventsDOM is populated on initial render and queryable directly. Same forpanel_timelineandpanel_details. Skip the tab-clicking step entirely.
5b. Output normalization — convert the human strings into ISO where possible. April 29, 2026 + 6:25 PM Local time → 2026-04-29T18:25:00 (no timezone offset is available from the page — DHL renders the carrier's local time without offset; pass through as-is or annotate "local_only": true). The destination string often duplicates the country ("France, France") when only the country is known; collapse identical comma-pairs client-side.
-
On not-found, return
{ success: false, reason: "tracking_not_found", tracking_number: "..." }. No retry — the same response repeats. Don't fallback to a search UI; DHL's tracking page is the only surface and a not-found at/utapiis authoritative across all DHL divisions (Express, Parcel, eCommerce, Global Forwarding). -
Release the session.
browse cloud sessions update "$sid" --status REQUEST_RELEASE
Site-Specific Gotchas
--verified --proxiesis mandatory. Without--verified(stealth fingerprint) AND--proxies(residential), Akamai's challenge iframes (#sec-cpt-if,#sec-text-if) never get auto-solved and theutapicall never resolves to data — the tracking page stays empty. Both flags are required; one alone is insufficient on bot-protected pages.- The
utapiendpoint is a TRAP — do not try to call it directly.https://www.dhl.com/utapi?trackingNumber=...&language=en&requesterCountryCode=US&source=ttreturnsHTTP 428 {"sec-cp-challenge": "true", "provider": "crypto", ...}for every external call, including from page-contextfetch()withcredentials: 'include'after the page has successfully loaded. The challenge token + nonce + difficulty (15000) demand a proof-of-work computation that's solved only by DHL's bundledbundle-utapi-logic.jsrunning inside an Akamai-cleared browser. Verified 2026-05-20 with 3 separate fetches from the same warmed session — all 428. Don't waste time on it; scrape the DOM. - The
developer.dhl.comTracking API requires registration + key. It's out of scope for unauthenticated agent flows. Don't mention it as a fallback unless the caller has a key. - All three tabpanels render simultaneously.
panel_details,panel_timeline,panel_eventsare all present and queryable in the DOM afterwait timeout 5000without any tab-click. The tabs use CSS visibility, not the HTMLhiddenattribute. Skip the tab-click step — queryingdocument.getElementById('panel_events').innerTextafter initial load returns the full event table. panel_eventsis the cleanest source for the event timeline — it's a 3-column table (Time / Status Update / Location) that flattens to predictable<date>\n<time>\n<status>\n<location>4-line groups.panel_timelinehas the same data but in a richer visual-card layout with extra whitespace; parsing is harder. Always preferpanel_events.- Status text uses participle phrases that need a location suffix. The event table has rows like
"In transit in"+ location"France"— meaning"In transit in France"when concatenated. Similarly:"Arrived at"+ location,"Departed from"+ location,"Picked up"+ location,"Parcel dropped off at DHL ServicePoint"+ location. Terminal statuses ("Delivered","Out for delivery") do NOT need the suffix. When emittingdescription, concatenate the status + location for transit/arrival/departure events. - Timestamps have no timezone offset. The page renders
"6:25 PM Local time"— "local" means the carrier's local time at the event location, not the user's timezone, and the offset is never exposed. Don't fabricate a UTC offset; preserve the human string or annotate the ISO with"local_only": true. - The tracking number echoes back digit-spaced in headers (
"1 2 3 4 5 6 7 8 9 0") for screen-reader accessibility. The unspaced original is also present ("Tracking Code: 1234567890") — match against the unspaced form, or strip whitespace before comparing. - "This shipment is handled by: X" identifies the DHL division that owns the shipment (
DHL eCommerce Iberia,DHL Express,DHL Parcel, etc.). WhenServicein the details panel is generic ("DHL PARCEL FOR YOU INTERNATIONAL"), the handler line is the more discriminating field for customer-service routing. /global-en/redirects to a country locale.https://www.dhl.com/global-en/home/tracking.html?tracking-id=...&submit=1→https://www.dhl.com/us-en/home/tracking.html?locale=true&tracking-id=...&submit=1(or another locale based on outbound IP). Localized paths render identical tracking data — only the page chrome / FAQ links change. To skip the redirect, hit/us-en/(or whichever locale is stable for the IP) directly.- Origin & Destination can be city-only or country-only. Observed:
Origin: "Córdoba"(city only, no country),Destination: "France, France"(city == country, label duplicated). Parse both fields tolerantly; never assume"city, country"shape. - The
Referencefield in the Details panel echoes back the input tracking number, not a customer reference. The true carrier-side ID isLocal Tracking Number(e.g.14 6000018487for the example shipment), which can be different from what the user pasted. Some divisions show both — preserve both in output. - Not-found is detected by string match, not HTTP status. The page returns HTTP 200 with the body text
"Sorry, your tracking attempt was not successful. Please check your tracking number."for any unrecognized tracking number — even malformed inputs like"ZZZZ9999INVALID". There is no separate 404 response. Check for this exact substring. JJD-and other prefixed formats follow the same flow.JJD000390000687283009was tested as not-found (no public-shipment match), but the URL pattern + extraction code handle it identically — no special-casing per format.- Page loads include 4 Akamai/CSP-related iframes (
#sec-text-if,#sec-cpt-if, plus two unnamed<iframe>containers and<iframe src="https://www.dhl.com/crypto/cca-new.html">). These are normal; if they're the only visible content + tab panels are absent after 8s, that's an unsolved challenge state. - Cookie consent banner does not block tracking results. The "Consent for Data Processing" overlay appears at page bottom but does NOT cover or delay the tracking result render. Skip clicking it.
Expected Output
Four distinct outcome shapes:
// 1. Success — package found, full timeline available
{
"success": true,
"tracking_number": "1234567890",
"status": "Delivered",
"handler": "DHL eCommerce Iberia",
"service": "DHL PARCEL FOR YOU INTERNATIONAL",
"origin": "Córdoba",
"destination": "France, France",
"last_update": "Wednesday, April 29, 2026 at 6:25 PM Local time, France",
"last_location": "France",
"local_tracking_number": "14 6000018487",
"weight": "5 kg",
"total_pieces": 1,
"events": [
{ "date": "April 29, 2026", "time": "6:25 PM Local time", "description": "Delivered", "location": "France" },
{ "date": "April 29, 2026", "time": "12:26 PM Local time","description": "Out for delivery", "location": "France" },
{ "date": "April 29, 2026", "time": "5:36 AM Local time", "description": "In transit in", "location": "France" },
{ "date": "April 29, 2026", "time": "3:15 AM Local time", "description": "Arrived at", "location": "France" },
{ "date": "April 28, 2026", "time": "10:57 PM Local time","description": "In transit in", "location": "France" },
{ "date": "April 28, 2026", "time": "6:22 AM Local time", "description": "Departed from", "location": "Barcelona" },
{ "date": "April 24, 2026", "time": "4:10 PM Local time", "description": "Departed from", "location": "Córdoba" },
{ "date": "April 24, 2026", "time": "1:19 PM Local time", "description": "Picked up", "location": "Córdoba" },
{ "date": "April 24, 2026", "time": "11:24 AM Local time","description": "Parcel dropped off at DHL ServicePoint","location": "Córdoba" }
],
"expected_delivery": null
}
// 2. Active shipment — same shape as #1, "status" is one of "In transit", "Out for delivery",
// "Arrived at <hub>", "Picked up", etc., and the most recent events[0] reflects the live state.
// `expected_delivery` may be populated when DHL provides an ETA on the page (rare for eCommerce,
// common for Express); when absent, leave null.
// 3. Not found
{
"success": false,
"reason": "tracking_not_found",
"tracking_number": "ZZZZ9999INVALID",
"detail": "Sorry, your tracking attempt was not successful. Please check your tracking number."
}
// 4. Anti-bot wall (only when session is misconfigured — e.g. missing --verified or --proxies)
{
"success": false,
"reason": "blocked",
"detail": "Akamai challenge iframe (#sec-cpt-if) persisted past 8s and no tabpanel rendered. Retry with `browse cloud sessions create --verified --proxies`."
}
Schema notes:
eventsis sorted newest-first as DHL renders it; preserve that order so consumers can readevents[0]for "most recent activity".descriptionfor transit / arrival / departure events is the participle phrase only ("In transit in","Arrived at","Departed from"); the noun (location) is in thelocationfield. Join with a space for human display:"In transit in France".expected_deliveryis generally null for DHL eCommerce / Parcel and populated for DHL Express; the page surface for ETA varies per carrier and is best-effort.weightandtotal_piecesare present for DHL eCommerce / Parcel and often absent for DHL Express; both default to null when not in the details panel.
How to use track-shipment on Cursor
AI-first code editor with Composer
Prerequisites
Before installing skills in Cursor, ensure your development environment meets these requirements:
- ›Cursor installed and configured on your development machine
- ›Node.js version 16.0+ with npm package manager (verify with
node --version) - ›Active project directory or workspace where you want to add track-shipment
Execute installation command
Execute the skills CLI command in your project's root directory to begin installation:
The skills CLI fetches track-shipment from GitHub repository dhl.com/track-shipment-18m2jq and configures it for Cursor.
Select Cursor when prompted
The CLI will show a list of available agents. Use arrow keys to navigate and space to select Cursor:
Verify installation
Confirm successful installation by checking the skill directory location:
Reload or restart Cursor to activate track-shipment. Access the skill through slash commands (e.g., /track-shipment) or your agent's skill management interface.
Security & Verification Notice
We perform automated surface-level scans (Gen AI Scanner, Socket, Snyk) during installation. These checks detect common vulnerabilities but do not guarantee complete security. Always review skill source code and verify the publisher's reputation before production use.
Skills execute code in your development environment. Always verify the publisher's identity, review recent commits, and test in isolated environments before production deployment.
List & Monetize Your Skill
Submit your Claude Code skill and start earning
Use Cases▌
Task Automation & Efficiency
Automate repetitive workflows and reduce manual effort
Example
Generate reports, summarize documents, draft communications
Save 3-5 hours per week on routine tasks
Knowledge Enhancement
Learn new skills, understand complex topics, get expert guidance
Example
Explain concepts, provide examples, suggest learning resources
Accelerate learning and skill development by 2x
Quality Improvement
Enhance output quality through reviews, suggestions, and refinements
Example
Review drafts, suggest improvements, catch errors
Improve work quality by 30-40% with less effort
Implementation Guide▌
Prerequisites
- ›Claude Desktop or compatible AI client with skill support
- ›Clear understanding of task or problem to solve
- ›Willingness to iterate and refine outputs
Time Estimate
15-45 minutes depending on use case complexity
Installation Steps
- 1.Install skill using provided installation command
- 2.Test with simple use case relevant to your work
- 3.Evaluate output quality and relevance
- 4.Iterate on prompts to improve results
- 5.Integrate into regular workflow if valuable
Common Pitfalls
- ⚠Expecting perfect results without iteration
- ⚠Not providing enough context in prompts
- ⚠Using skill for tasks outside its intended scope
- ⚠Accepting outputs without review and validation
Best Practices▌
✓ Do
- +Start with clear, specific prompts
- +Provide relevant context and constraints
- +Review and refine all outputs before using
- +Iterate to improve output quality
- +Document successful prompt patterns
✗ Don't
- −Don't use without understanding skill limitations
- −Don't skip validation of outputs
- −Don't share sensitive information in prompts
- −Don't expect skill to replace human judgment
💡 Pro Tips
- ★Be specific about desired format and style
- ★Ask for multiple options to choose from
- ★Request explanations to understand reasoning
- ★Combine AI efficiency with human expertise
When to Use This▌
✓ Use When
Use when skill capabilities match your task, clear ROI on time saved, and you can validate outputs. Best for repetitive tasks, learning, and quality improvement.
✗ Avoid When
Avoid when task requires deep expertise you can't validate, involves sensitive decisions, or when learning process is more valuable than speed of completion.
Learning Path▌
- 1Familiarize yourself with skill capabilities and limitations
- 2Start with low-risk, non-critical tasks
- 3Progress to more complex and valuable use cases
- 4Build expertise through regular use and experimentation
Discussion
Product Hunt–style comments (not star reviews)- No comments yet — start the thread.
Ratings
4.6★★★★★45 reviews- ★★★★★Naina Rahman· Dec 28, 2024
track-shipment has been reliable in day-to-day use. Documentation quality is above average for community skills.
- ★★★★★Isabella Menon· Dec 16, 2024
track-shipment is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.
- ★★★★★Dhruvi Jain· Dec 12, 2024
track-shipment fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.
- ★★★★★Benjamin Liu· Dec 8, 2024
track-shipment fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.
- ★★★★★Ama Martinez· Dec 4, 2024
I recommend track-shipment for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.
- ★★★★★Hiroshi Park· Nov 27, 2024
Registry listing for track-shipment matched our evaluation — installs cleanly and behaves as described in the markdown.
- ★★★★★Xiao Gonzalez· Nov 23, 2024
Keeps context tight: track-shipment is the kind of skill you can hand to a new teammate without a long onboarding doc.
- ★★★★★Rahul Santra· Nov 11, 2024
track-shipment has been reliable in day-to-day use. Documentation quality is above average for community skills.
- ★★★★★Mateo Lopez· Nov 7, 2024
Useful defaults in track-shipment — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.
- ★★★★★Oshnikdeep· Nov 3, 2024
Registry listing for track-shipment matched our evaluation — installs cleanly and behaves as described in the markdown.
showing 1-10 of 45