Skip to content
Use cases

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.