Skip to content

About

Native Home Assistant integration for parcel tracking — InPost, DPD, FedEx, Pocztex, DHL

Topics

Resources

Stars

7 stars

Watchers

0 watching

Forks

Latest commit

 

History

45 Commits

Folders and files

Repository files navigation

Shipment Tracking for Home Assistant

Open in HACS

HACS Custom Home Assistant deps: segno License Made in Poland

Track your parcels natively in Home Assistant — InPost, DPD, FedEx, Pocztex, DHL, Orlen Paczka and Allegro One — ready-to-pickup, in-transit and archive, as first-class entities.


✨ Features

  • 📥 Do odbioru (InPost) — how many parcels are ready to pick up, with sender, locker, pickup code, expiry and QR payload in attributes
  • 🚚 W drodze (both carriers) — parcels on the way, with human-readable Polish statuses
  • 🗄️ Archiwum / dostarczone — recently delivered / closed parcels (capped, configurable)
  • 👥 Multi-account — add several accounts per carrier, each as its own device
  • 🤝 App-to-app sharing (InPost only) — hand a ready parcel to another configured InPost account (a paired "friend"), on a button press or automatically
  • 🔑 Official mobile/web auth, no scraping — InPost signs in on its own page in your browser (OAuth2 + PKCE, you paste back the final address); DPD/DHL use SMS login (DHL's stores a session-cookie snapshot instead of a refresh token, verified to survive a real sliding 30-min window). Pocztex is the exception: its Keycloak session has a hard 30-minute cap that no amount of refreshing can extend, so its config entry stores the account password and re-logs-in every poll instead
  • 🧩 Almost dependency-free — stdlib urllib client for both carriers; the only requirement is segno, for rendering InPost pickup QR codes
  • 🏷️ Every sensor wears its carrier's own logo — Home Assistant allows one brand image per integration, so all five carriers would otherwise share one generic parcel box; each carrier's sensors carry the carrier badge as entity_picture instead
  • 🎯 One framework, one entity shape per carrier — adding a new carrier module doesn't touch the others

🧭 How it fits together

Five carriers, each with its own auth cycle, feed their own coordinator; all coordinators converge on one dispatch point in sensor.py; InPost fans out to four HA platforms, the other four carriers to one.

Every carrier authenticates and polls on its own terms — the divergence in the left two columns above is real, not simplified away (Under the hood below has the specifics). They all land on the same dispatch point in sensor.py, which is exactly the seam that broke twice in this project's history (an un-dispatched carrier crashing on the first real DPD account, then again for DHL) — every new carrier module has to be wired in there or it inherits InPost's entity shape by accident.

📦 Entities

Mock-up of the InPost 'Do odbioru' and DHL 'W drodze' sensor cards, with placeholder sender/locker/code data — not a real account.

Illustrative mock-up — placeholder senders and codes, not a real account.

InPost (per account)

Entity State Key attributes
sensor · Do odbioru number ready to pick up — own + shared with me do_odbioru_count, w_drodze_count, do_odbioru[] (nadawca, kod odbioru, paczkomat, adres, termin, qr), w_drodze[]
sensor · W drodze number in transit — own parcels only —
sensor · Udostępnione active parcels shared with this account by someone else udostepnione[] (numer, nadawca, status, od, kod odbioru, paczkomat, podglad), moje_udostepnione[] (the other direction, with dla), both _counts
sensor · Archiwum number archived archiwum[] (latest N)
image · QR do odbioru (×6 slots) pickup QR per group (multiskrytka collapses to one) —

The qr payload lets a Lovelace card render the compartment-opening QR client-side. Each do_odbioru[] row also reports its sharing state: wlasciciel (OWN / FRIEND / OBSERVED), udostepniona_do and mozna_udostepnic.

DPD (per account)

Entity State Key attributes
sensor · W drodze number of active (not yet delivered) parcels active_count, delivered_count, w_drodze[] (numer, nadawca, status, aktualizacja), dostarczone[] (latest N)

DHL (per account)

Entity State Key attributes
sensor · W drodze number of active (not yet delivered) parcels — own plus parcels shared to this account active_count, delivered_count, w_drodze[] (numer, nadawca, status, aktualizacja, udostepniona), dostarczone[] (latest N)

Only one canonical status is confirmed live (TT_DOR → "Dostarczona") — the research account had a single, already-delivered parcel. Any other status code shows up as its raw DHL string rather than a guessed translation; see const.py's DHL section.

FedEx (per configured tracking-number list)

Entity State Key attributes
sensor · W drodze number of active (not yet delivered) tracking numbers active_count, delivered_count, w_drodze[] (numer, status, nadawca_miasto, nadawca_kraj, usluga), dostarczone[] (latest N)

No auto-discovery — FedEx's official Track API is track-by-number only, so the tracked numbers are a manually maintained list in this entry's options, not an account. A number stays tracked (and counted) whether delivered or not until removed from that list.

Pocztex (per account)

Entity State Key attributes
sensor · W drodze number of active (not yet delivered) parcels active_count, delivered_count, w_drodze[] (numer, status, postep, aktualizacja), dostarczone[] (latest N)

Poczta Polska's own state field is already a Polish label — shown as-is, no canonical-status mapping needed. postep is the raw 0-100 progress percentage the API returns; anything under 100 counts as active.

Orlen Paczka (per configured tracking-number list)

Experimental. Status labels come back as free text and are mapped by keyword; they have not been checked against real parcels yet. An unrecognised label keeps the parcel active rather than archiving it.

Entity State Key attributes
sensor · W drodze number of active (not yet delivered) tracking numbers active_count, delivered_count, w_drodze[] (numer, status, aktualizacja), dostarczone[] (latest N)

Public JSONP track-by-number (no Partner SOAP secrets). Same Options list pattern as FedEx. Details and risk notes: docs/ORLEN_ALLEGRO_ONE.md. Sensors use mdi:truck-delivery (no badge PNG yet).

Orlen Paczka (konto) (per phone number)

Experimental. Signs in to the ORLEN Paczka app backend with your phone number and an SMS code, then tracks every parcel addressed to that number; no tracking-number list. The per-parcel field names are best-effort until a real parcel has been seen.

Entity State Key attributes
sensor · W drodze number of active parcels w_drodze[], dostarczone[] (latest N)
sensor · Do odbioru number of parcels ready for pickup do_odbioru[]

The integration does not ship the app credentials. Setup asks for the client secret of the ORLEN Paczka app's Entra ID (Azure AD) registration, next to the phone number; it is stored on the config entry only. A missing or rejected secret starts reauthentication, which asks for it again before the SMS step. You supply the secret yourself; this repository does not publish it.

Allegro One (per configured tracking-number list)

Experimental. Status labels come back as free text and are mapped by keyword; they have not been checked against real parcels yet. An unrecognised label keeps the parcel active rather than archiving it.

Entity State Key attributes
sensor · W drodze number of active (not yet delivered) tracking numbers active_count, delivered_count, w_drodze[] (numer, status, aktualizacja), dostarczone[] (latest N)

Public edge track-by-number (no Allegro OAuth). Use Allegro-internal waybills (A… / AD…). Undocumented internal Accept header — production risk; see docs/ORLEN_ALLEGRO_ONE.md. Sensors use mdi:truck-delivery (no badge PNG yet).

🤝 Sharing a parcel with another account (InPost only)

Configure two InPost accounts and each device gains one entity per other InPost account:

Entity What it does
button · Udostępnij → <alias> shares every active parcel not already shared with that account
switch · Auto-udostępnianie → <alias> keeps doing it for each new parcel, on every poll

Sharing covers every active parcel, in transit included — InPost allows it long before the parcel reaches the locker, so the peer follows the whole journey and gets the pickup code the moment it exists.

The recipient's account then lists those parcels normally — including pickup code and QR — so their sensors, QR image entities and cards need no extra wiring.

The three InPost counters are designed not to overlap, so two mirrored accounts never report the same parcel twice:

Do odbioru    = own ready      + shared-with-me ready
W drodze      = own in transit
Udostępnione  = shared with me, active

Prerequisites and limits:

  • The two InPost accounts must already be paired in the InPost mobile app (Settings → Sparuj użytkownika, invitation code). Pairing cannot be done over this API; until it is done, both entities stay unavailable.
  • InPost decides per parcel whether sharing is allowed (operations.canShareParcel); parcels it refuses are skipped.
  • Sharing is not undone here. Turning the switch off stops new shares; it does not withdraw parcels already shared. Withdrawing is an app-side action.
  • Adding a further account needs no manual step: the new one gets its sharing entities straight away, and every already-running account is reloaded once so it gains the entities aimed at the newcomer. Removing an account takes those entities back from its peers the same way.
  • DPD has no equivalent app-to-app sharing API — the button/switch entities only ever appear on InPost devices.

🚀 Installation

HACS (recommended)

  1. HACS → ⋮ → Custom repositories → add https://github.com/jrx-code/hassio-integration-shipment-tracking as Integration — or just click the Open in HACS badge above.
  2. Install Shipment Tracking (InPost, DPD, FedEx, Pocztex, DHL, Orlen Paczka, Allegro One), then restart Home Assistant.

Manual

Copy custom_components/shipment_tracking/ into your Home Assistant config/custom_components/ and restart.

⚙️ Configuration

Settings → Devices & Services → Add Integration → Shipment Tracking, then pick a carrier:

InPost:
1.  Alias        →  e.g. "Home"
2.  Sign in      →  open the link shown, sign in on InPost's page
                    (phone or e-mail), copy the address the browser ends on
                    (account.inpost-group.com/callback?code=...) and paste it

DPD:
1.  Alias        →  optional
2.  Phone        →  9 digits
3.  SMS code     →  sent to that number (DPD Mobile)

DHL:
1.  Alias        →  optional
2.  Phone        →  9 digits
3.  SMS code     →  sent to that number (Mój DHL)

FedEx:
1.  Alias           →  optional
2.  API Key          →  Client ID from a project in your FedEx developer org
3.  Secret Key       →  Client Secret
4.  Account number   →  optional, informational
    (tracking numbers are added afterwards, in Options)

Pocztex:
1.  Alias        →  optional
2.  E-mail       →  existing Pocztex Mobile account (app-only registration)
3.  Password

Orlen Paczka / Allegro One (same shape as FedEx track-by-number):
1.  Alias           →  optional
    (tracking numbers are added afterwards, in Options — see docs/ORLEN_ALLEGRO_ONE.md)

When a session expires, Home Assistant starts a re-auth for that carrier — the browser sign-in again for InPost, a fresh SMS for DPD/DHL, the password form again for Pocztex.

InPost sign-in, details. InPost retired its SMS login on 2026-10-12; the app now signs in with OAuth2 + PKCE behind a Cloudflare captcha and only redirects to its own callback page, so Home Assistant cannot finish the login by itself. Paste the address right away, the code expires within minutes. For several InPost accounts use a private/incognito window per account (InPost's page remembers the signed-in account). Accounts added with the old SMS login ask for this sign-in once after the update; entities and their ids stay as they were. The phone number is read from InPost's token or, when the token has none (e-mail sign-in may give one), from the receiver of the account's own parcels; a re-auth only accepts the same account. Signing in with e-mail (code or password) ends on the same callback page as the phone + SMS sign-in, so the paste step is identical. Per-entry options: polling interval (default 15 min), archived/delivered-parcels cap; FedEx / Orlen Paczka / Allegro One additionally have their tracking-number list there, InPost an ignored-shipment-numbers list (hides a stuck/zombie record InPost's own app stopped showing — see Entities above).

🔧 Under the hood

  • InPost OAuth2 + PKCE (client_id=inpost-mobile): browser sign-in on account.inpost-group.com (Cloudflare Turnstile), code and refresh at POST /global/oauth2/token on the mobile API host. The access token is cached until shortly before it expires; a rotated refresh token is written back to the entry. The legacy SMS endpoints (/v1/account, /v1/authenticate) answer 404 since 2026-10-12.
  • DPD SMS auth via Keycloak (dpdsso.dpd.com.pl, realm DPD) against the DPD Mobile PL backend — not the GEOPOST myDPD/email+password platform.
  • DHL SMS auth behind an Altcha proof-of-work captcha, solved client-side (brute-force SHA-256(salt+n)==challenge, a fraction of a second — not an image to click through). Session lives in httpOnly cookies set at login, refreshed with a genuine sliding 30-minute window on every poll.
  • Pocztex email+password auth via Keycloak authorization_code+PKCE (idm.pocztex.pl, realm ppsa) — direct password grant is disabled for this client, so it drives the same browser-less PKCE dance a login page would. Its session has a hard, non-sliding 30-minute cap: refreshing a token doesn't extend it, so this carrier re-logs-in every poll instead (the config entry stores the account password for that, not just a refresh token).
  • FedEx uses the official developer.fedex.com Track API (OAuth2 client_credentials) — the one carrier here that isn't a reverse-engineered consumer app.
  • Orlen Paczka uses the public JSONP status endpoint (no Partner SOAP). Allegro One uses edge.allegro.pl with an internal Accept media type — fragile by design; see docs/ORLEN_ALLEGRO_ONE.md.
  • ETag pagination on InPost's /v4/parcels/tracked — InPost (ab)uses ETag/If-None-Match as a page cursor; a naive single GET misses recent parcels.
  • Blocking urllib clients for every carrier, driven from Home Assistant's executor; InPost 304 responses keep the last snapshot.
  • Carrier badges are 256² PNGs served by the integration itself at /shipment_tracking/logo/<carrier>.png and pointed at by entity_picture. The frontend crops a picture to a circle, so each badge puts the mark on the carrier's own colour with the mark inscribed in that circle — a bare wordmark would be cut down to its middle stripe.
  • Every carrier's unique_ids and device identifiers are carrier-prefixed (inpost_<phone>_..., dpd_<phone>_..., dhl_<entry_id>_..., ...) so the same phone number used on two carriers never collides.

🚧 Roadmap

Next carriers, in order: UPS, DHL by number (official Shipment Tracking - Unified API) and GLS (#6), then an optional aggregator for cross-border parcels (AliExpress, Temu, Shein). Why these, the sources, and what was ruled out (Amazon, Vinted Go, Packeta, Cainiao lockers, X-press): docs/CARRIER_ROADMAP.md. Delivery windows on the sensors: #7.

Plan: UPS

Same shape as FedEx: the user's own developer app credentials, tracking numbers in Options, no account auto-discovery (UPS has no consumer "my parcels" API).

What UPS's official OpenAPI specs say (UPS-API/api-documentation, OAuthClientCredentials.yaml, Tracking.yaml):

  • Token: POST https://onlinetools.ups.com/security/v1/oauth/token, HTTP Basic with the Client ID and Secret, grant_type=client_credentials. The spec describes this flow for an integration owner who is also the UPS shipper; the x-merchant-id header (6-digit account number) is optional.
  • Track: GET https://onlinetools.ups.com/api/track/v1/details/{inquiryNumber}, one number per request (FedEx takes 30), required headers transId and transactionSrc, optional locale. A test environment exists at wwwcie.ups.com.
  • Retention: data rolls off after 120 days.
  • Delivery windows: deliveryTime carries an estimated, confirmed or imminent window (EDW, CDW, IDW, with startTime/endTime), and deliveryDate the scheduled or rescheduled date (SDD, RDD). Direct input for #7.

Steps:

  1. Spike, go/no-go. Create an app on developer.ups.com and check (a) whether a free UPS account is enough, without a shipper account number, and (b) whether Track returns an inbound 1Z number shipped by someone else. The specs state neither. If (b) fails, stop: the module would be useless to a recipient.
  2. api_ups.py: stdlib urllib client run in the executor; token cached until it expires; one GET per number; 404 means "not in the system yet", not an error.
  3. Status mapping: ups_canonical() in const.py, from currentStatus onto the shared buckets, built from UPS's Tracking appendix. Unknown codes fall back to a keyword scan and stay active, the rule DHL and InPost already follow.
  4. Coordinator and sensor modelled on FedEx, plus okno_doreczenia_od / okno_doreczenia_do and przewidywana_data from deliveryTime / deliveryDate.
  5. Config flow step ups (alias, Client ID, Client Secret), tracking numbers in Options, translations, a UPS badge in logos/.
  6. Polling cost: one call per number per interval, so a number in a final state is no longer polled, and numbers past the 120-day retention are dropped.
  7. Tests and docs: normalizer fixtures from the spec examples and from the spike's responses (redacted); Entities, Configuration and Under the hood sections here.

⚠️ Disclaimer

Unofficial integration, not affiliated with or endorsed by InPost, DPD, FedEx, DHL, Poczta Polska/Pocztex, Orlen Paczka, or Allegro. For InPost/DPD/Pocztex/DHL it talks to each carrier's consumer mobile/web API on your behalf using your own account; FedEx uses their official, documented developer API instead; Orlen Paczka uses a public JSONP status endpoint; Allegro One uses an undocumented public edge endpoint (operator accepts breakage risk — see docs/ORLEN_ALLEGRO_ONE.md). Use it at your own discretion. All carrier names and logos belong to their respective owners.

📄 License

MIT © JI ENGINEERING

About

Native Home Assistant integration for parcel tracking — InPost, DPD, FedEx, Pocztex, DHL

Topics

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages