Tracking status from carrier portals that have no API
Regional carriers and freight forwarders publish shipment status in a portal and nowhere else. A workflow signs in, looks up your consignments and returns the status fields your customer service needs.
From consignment list to status feed
Run it every hour during the day, or trigger it from your system when a customer asks.
-
Vault the carrier login
One credential per carrier portal, referenced by id from the workflow. The password is typed only on the carrier’s own site.
-
Pass the consignment numbers in
Workflow inputs carry the list of shipments to look up. A loop block walks the list and looks each one up on the portal’s tracking page.
-
Extract the status fields
Per shipment: current status, last scan location, timestamp, expected delivery. The extraction returns JSON against the schema and marks anything it could not read.
-
Post to your tracking view
An http_request block sends the collected statuses to your endpoint. Customer service sees one table instead of five portals.
Look up a list of consignments
import { Browserberg } from '@browserberg/sdk';
const bb = new Browserberg({
apiKey: process.env.BROWSERBERG_API_KEY,
baseUrl: 'https://browserberg.com',
});
const wf = await bb.workflows.publish({
title: 'Carrier portal: consignment status',
parameters: [{ key: 'consignments', description: 'Consignment numbers to look up', required: true }],
blocks: [
{ blockType: 'login', label: 'Sign_in', credentialId: 'cred_carrier_portal' },
{ blockType: 'loop', label: 'Each_consignment', items: '{{ consignments }}', blocks: [
{ blockType: 'navigation', label: 'Open_tracking', url: 'https://portal.carrier.example/track/{{ item }}' },
{ blockType: 'extraction', label: 'Read_status',
instruction: 'the shipment status: state, last scan location, last scan time, expected delivery',
schema: { type: 'object', properties: { state: { type: 'string' }, location: { type: 'string' },
scannedAt: { type: 'string' }, eta: { type: 'string' } } } },
] },
{ blockType: 'http_request', label: 'Publish', method: 'POST',
url: 'https://tracking.internal.example/carrier-status', body: '{{ Each_consignment_output }}' },
],
});
const session = await bb.sessions.create({ ttlSeconds: 900 });
const run = await bb.workflows.run(wf.workflowId, {
sessionId: session.id,
inputs: { consignments: ['DE4471120033', 'DE4471120041'] },
});
console.log(run.status, run.outputs);
Details
| Inputs | Declared workflow parameters, validated at start; templates read them as {{ name }} |
|---|---|
| Loop | A loop block iterates a list; each iteration’s blocks publish their own output |
| Session | Any session you create, or a reserved pool for predictable start times |
| Failure | One consignment failing names its block; the run status says whether the rest continued |
| Evidence | Run record plus the signed action log; both carry the run id |
| Residency | EU-hosted browser, EU-pinned extraction tier by default |
As of 2026-09-04
Questions logistics teams ask
The carrier has an API. Should I still do this?
No. Use the API. A browser is the right tool only where the portal is the only interface, which for smaller carriers and forwarders is still common.
How fresh is the status?
As fresh as your schedule. Hourly is typical; a webhook trigger lets your system request a look-up on demand when a customer calls.
Can it handle a portal that shows one shipment per search?
Yes, that is what the loop is for: navigate to the search, act to enter the number, extract the result, repeat. Each iteration is recorded separately.
What about the tracking pages of the big parcel carriers?
If the carrier offers a tracking API, use it. If the page is public and permits automation, a plain extraction works. Browserberg does not add stealth to get past a carrier that blocks bots.
Bring your slowest carrier portal
Five browser hours, no card. One login, one loop, one endpoint.