Per-Order Shipping Cost Allocation for Multi-Carrier Ecommerce

Allocate true shipping cost per order across DHL, DPD, GLS, and other carriers — with surcharge rules, multi-package handling, and a pan-EU spreadsheet schema.

Published
Updated
Reading Time
34 min
Topics:
Industry GuidesLogisticsEUparcel shippingecommerce shipping reconciliationshipping margin analysiscarrier invoice allocation

Most ecommerce brands running three or more carriers across EU markets can tell you what they spent on shipping last month — total. They cannot tell you what each order cost to ship. The gap between those two numbers is where contribution margin lives, and closing it is what per-order shipping cost allocation produces.

Per-order shipping cost in multi-carrier ecommerce is computed by joining each carrier invoice line to an order through the tracking number, then allocating surcharges by type: base rate and fuel proportional to the line, residential and oversize flat to the triggering consignment, toll proportional to base across the routes that triggered it. Reconciling the sum of per-order costs back to the carrier invoice total within rounding tolerance confirms the allocation is complete. The resulting per-order cost column feeds true contribution-margin analysis at order, SKU, and channel level.

That is the methodology this article walks. It is not a billing-audit methodology. The two workflows look superficially similar — both consume the same monthly carrier invoice — but they answer different questions. Billing-audit work asks where the carrier got the bill wrong: missed credits, surcharges that shouldn't apply, dim-weight billing errors, contract-rate deviations. The deliverable is a list of disputes. Margin-analysis work asks what each unit of revenue cost to fulfil, broken down to the order and the SKU. The deliverable is a column on the order/sale/COGS table. If you came here looking for the audit angle, the parcel carrier invoice audit at shipment level walkthrough is the right companion piece. If you came here to compute true per-order cost, keep reading.

The default operating assumption from here on is multi-carrier and pan-EU. A typical mid-size brand at this stage runs DHL DE for German domestic, DPD FR/UK for France and the UK, GLS BENELUX for the Low Countries, Bartolini IT for Italy, SEUR ES for Spain, plus 3PL passthroughs covering whatever isn't handled directly. Five carrier invoices, five formats, in three or four languages, with surcharge sublines named differently in each — Treibstoffzuschlag on DHL, surcharge gazole on DPD France, Kraftstoffzuschlag on GLS, all of them the fuel surcharge by another name. Single-carrier examples are degenerate cases. US-only framing is wrong for this audience.

A note on what does not count as per-order cost. Dividing total monthly shipping spend by total order count produces an average. The average has its uses — it lands in a board deck, it shows year-over-year movement, it sets a budget. It does not tell you anything about per-SKU economics. Different SKUs ship to different destinations, in different package sizes, triggering different fuel and residential and toll surcharges; collapsing that variance into a single average blurs exactly the signal margin analysis needs. A bulky low-margin SKU shipping residential to remote zip-code ranges costs orders of magnitude more to fulfil than a small high-margin SKU shipping commercial-address domestic. The averaged-cost shortcut hides that difference. The point of this work is to surface it.

The deliverable, end to end, is a per-order shipping cost column on the order/sale/COGS table — one figure per order, fed by allocated carrier-invoice lines, joinable to the SKU dimension through the package manifest and to the channel dimension through the order's source-system field. With that column in place, contribution margin per order, per SKU, and per channel becomes a pivot-table answer rather than a quarterly project.


Freight-out: cost of revenue or operating expense?

Before the per-order column is built, settle where it lives in the P&L. The classification question turns on the distinction between freight-in and freight-out — inbound carriage on goods you bought, versus outbound delivery to the customer who bought from you. The two land in different places under accounting standards.

Inbound freight is capitalised into inventory. Per IAS 2 paragraph 11 on the costs of purchase of inventories, the costs of purchase of inventories comprise the purchase price, import duties and other taxes, and transport, handling and other costs directly attributable to the acquisition of finished goods, materials and services — meaning inbound freight (carriage inwards) is capitalised into inventory cost, while outbound freight to end customers sits outside this definition and is recognised as a period expense. The standard's plain reading puts customer-delivery cost outside cost of inventory.

That leaves outbound freight as a period expense. The default GAAP and IFRS treatment groups it with operating expenses, often as a selling or distribution line. The treatment is defensible, audit-friendly, and what most general-ledger templates assume out of the box. It also produces a gross margin figure that excludes shipping cost, which is the source of the trouble: for a D2C ecommerce brand whose ship-to-customer cost can run 8–15% of revenue, gross margin without shipping included flatters the unit economics by exactly that amount.

The ecom-accountant convention works around this by classifying outbound freight as cost of revenue — sitting in the COGS block in the P&L rather than below the gross-margin line. The figure in the financial statements is the same; its location is different. Gross margin then reads as gross margin after fulfilment, which is the number an ecommerce operator actually wants to see when comparing channels, evaluating SKU profitability, or pricing a promotion. For ecom margin work, classify as cost of revenue.

The trade-off is real. Strict GAAP/IFRS practitioners — auditors of larger entities, lenders working from standardised financial covenants — may push back on the reclassification. Most ecom-specialist accountants handle this by disclosing the policy in the notes and, where the audience demands it, presenting the figure both ways: gross margin per the standard line-item position, and gross margin after fulfilment for management reporting. The classification debate doesn't excuse skipping the allocation either way. Whether the per-order shipping cost column lands in COGS or in operating expenses, you still need the per-order figure to compute meaningful gross or contribution margin. The location is policy. The number is allocation.


The tracking number is the join key — and where it breaks

Per-order allocation lives or dies on the join from carrier-invoice line back to order. The join has four hops, and the tracking number is the only stable key linking the carrier side to the brand side:

Order ID ↔ Shipment ID ↔ Tracking Number(s) ↔ Carrier-Invoice Line.

Each hop pulls from a different system. The order ID lives in the ecommerce platform — Shopify, BigCommerce, WooCommerce, Magento, with each platform's own column name (order_id, name, increment_id). The shipment ID lives in the order-management or label-printing layer — Sendcloud, ShipStation, Shippo, EasyPost, or the platform's own shipping module — and is what binds an order to one or more physical consignments. The tracking number is generated when the label is printed and gets stamped onto both the parcel and the carrier-invoice line. The invoice line itself lives in the carrier's monthly billing PDF or CSV.

SKU, weight, addressee, and value are all data points carriers and brands share, but none of them survive as a join key at scale. Two orders to the same address in the same week with the same SKU mix collide on every column except the tracking number. The carrier doesn't know about your order ID; the brand doesn't know about the carrier's internal consignment ID. The label-printed tracking number is the one identifier both sides record, which is why every column on the right side of the spreadsheet — base, fuel, residential, toll, total — has to attach to a tracking number first and roll up to the order from there.

That logic works cleanly for the canonical case: one order, one parcel, one tracking number, one invoice line. Real data is messier in five recurring ways, and the spreadsheet has to handle each of them or the per-order numbers will be wrong wherever they occur.

Multi-package orders. A fashion brand splits a winter coat and a pair of boots across two parcels, generating two tracking numbers under one order. The carrier invoice has one line per consignment, so the join from invoice back to order is many-to-one. Brands that export tracking_number as a single field per order — taking the first one or the last one — lose every secondary parcel. The order/shipment table has to expose every tracking number per order, with a row per consignment.

Returns labels. A returns label is its own tracking number, often issued at the same time as the outbound label and printed on the same packing slip. It usually bills on a separate invoice line, sometimes on a separate carrier account entirely, and it belongs to a returns-cost column rather than to the original order's outbound shipping cost. Mixing returns lines into the outbound per-order figure double-counts cost on the orders that triggered returns and understates the brand's true returns burden. Flag returns labels at the join.

B2B consolidated shipments. A wholesale or marketplace dropshipping flow ships ten orders for one customer in one consignment under one tracking number. The carrier sees one line; the brand has ten orders. The join is many-to-many, and resolving it requires apportioning the consignment cost across the orders within it — usually by weight share or unit-count share. Without that apportionment step, ten orders share one tracking number and the per-order cost is either zero (for nine of them) or the full consignment cost (for one of them, if you're sloppy).

Label-printed-but-not-shipped voids. A label is generated, a tracking number is created, the parcel is voided before the carrier picks it up. Some carriers credit the line on the next invoice. Some don't, and the void shows up as a billed line with no actual movement. The tracking number is on the order and on the invoice, but no shipment occurred. The reconciliation has to recognise voids and exclude them from the per-order cost while still tying back to the carrier total once credits are applied.

Mid-month carrier handoffs. Brands running multi-carrier rate-shoppers (cheapest carrier per zone, often through a label-printing platform) sometimes have a label printed on Carrier A and the parcel rerouted to Carrier B mid-network. The tracking number recorded against the order may not be the tracking number the brand is invoiced for. The shipping platform's reconciliation feed is the recovery path: it carries both the original and the actual carrier identifiers per shipment. Brands without that feed lose the handoffs at the join.

The practical implication is a data-export discipline. The order/shipment table the analyst pulls each month has to include every tracking number per order, not just the first; has to flag returns, voids, and B2B consolidations; and has to record the carrier of record per consignment. The tracking-number-to-order mapping is the precondition for everything that follows. If the export is wrong, the allocation rules in the next section operate on the wrong rows.

Most brands building this for the first time work in Shopify, and the practical question becomes how to match carrier invoice to Shopify orders across multiple carriers. Shopify's Orders and Fulfillments objects expose the order ID, the shipment-level fulfilment record, and the tracking number array per fulfilment. The same join logic applies on BigCommerce, WooCommerce, and Magento with platform-specific column names; the shape of the data is consistent across them.

Allocating surcharges by type: fuel, residential, oversize, toll

With the tracking-number join in place, the next step is allocating each surcharge family to the right consignment. Carrier invoices don't ship as flat per-line totals; they ship as a base rate plus a stack of surcharge sublines, some attached to a specific shipment, some applied at the invoice level. The allocation rule per surcharge type is what determines whether the surcharge attaches to the consignment that triggered it, distributes proportionally across consignments, or follows some other rule.

There are two allocation patterns. Proportional to base spreads the surcharge across consignments by their share of total base cost — the right rule for surcharges that apply to the whole invoice or to a defined subset of routes. Flat to the triggering consignment attaches the surcharge to the one tracking number that caused it — the right rule for per-shipment fees triggered by an attribute of that shipment. Each surcharge family falls into one pattern or the other.

Base shipping cost. Direct mapping. The base rate on the carrier invoice line is identified by tracking number; allocate it to that consignment. No formula required beyond the join.

Fuel surcharge — Treibstoffzuschlag, surcharge gazole, Kraftstoffzuschlag. Most European carriers calculate fuel as a percentage of the base plus other surcharges, indexed against a published monthly diesel reference. DHL, DPD, and GLS each publish their own monthly fuel surcharge index. The fuel charge appears on the invoice in two shapes: per-line, listed alongside each base rate, or invoice-level, totalled at the bottom. When per-line, allocate to the corresponding tracking number — you already have it. When invoice-level, allocate proportional to base by line:

surcharge_per_line = (base_for_line / sum(base_across_lines_in_scope)) * total_surcharge

For a fuel charge that applies to all lines on the invoice, "in scope" is every consignment on that invoice. The formula is the workhorse of proportional allocation; everything in this section that isn't flat-to-triggering-consignment uses some version of it.

Peak / seasonal surcharge. Same shape as fuel. Peak applies during Q4, Chinese New Year, and other defined windows; carriers either break it out per line or aggregate it. Same rule: per-line maps to the tracking number; invoice-level allocates proportional to base across lines that fall in the peak window.

Residential surcharge — Privatadresse-Zuschlag, supplément résidentiel. Triggered by the delivery address being a private residence rather than a commercial address. Flat to the consignment that triggered it. The surcharge belongs to that one tracking number; do not distribute it. Brands that miss this often have a stronger problem upstream: the order's residential/commercial classification isn't on the order export, so the spreadsheet has no column to flag it, and residential surcharge per order Shopify reconciliation comes out with unexplained variance because there's no field to tie the surcharge back to the order's address type. The fix is on the order-side export, not the allocation rule. Pull the address classification (Shopify exposes it via the address object; many label platforms classify automatically and write it back) and join on it.

Oversize — Sperrgut, hors gabarit. Per-package fee for parcels exceeding dimensional or weight thresholds. Carriers each set their own break points (DHL has volumetric thresholds; GLS publishes a weight-and-girth limit; DPD applies an oversize fee above defined dimensions). Flat to the triggering consignment.

Address correction — Adressberichtigung, correction d'adresse. Charged when the carrier rewrites a malformed or incomplete delivery address en route. Flat to the triggering consignment. These surcharges also flag a data-quality issue worth tracking — a rising address-correction rate usually means the checkout's address validation is weakening.

DAS — Delivery Area Surcharge. Applies to specific zip-code ranges (extended areas, remote islands, hard-to-reach postal regions). Flat to the triggering consignment. International express services (DHL Express, FedEx International, UPS WorldShip) carry this as a recurring line.

Toll surcharge — Mautzuschlag, péage. Charged for routes that pass through tolled road segments — common on the German Maut and the French péage networks. Carriers handle this two ways. Some break it out per route segment, in which case allocate proportional to base across the consignments that actually routed through that segment:

toll_per_line = (base_for_line / sum(base_for_lines_routed_through_segment)) * total_toll_for_segment

Others apply it as an invoice-level surcharge with no segment-level detail, in which case allocate proportional to base across all lines whose destination would plausibly route through tolled infrastructure. The latter is less precise; where the carrier provides segment detail, use it.

The "in scope" qualifier in the proportional formula is the part that matters most. Fuel applies to all lines on the invoice; toll applies only to consignments routed through the toll segment; peak applies only to consignments shipped during the peak window. Wherever the scope is anything narrower than the whole invoice, the denominator in the proportional formula has to match the scope — sum of base costs for the consignments the surcharge actually applies to, not the invoice total. Getting the denominator wrong is a common cause of failed reconciliation.

A note on dimensional weight, which sits adjacent to the surcharge stack but isn't a separate line. Dim weight is a billing-weight rule: chargeable weight equals the maximum of actual weight and dimensional weight (volume divided by a carrier-specific divisor). When dim weight exceeds actual, the base rate is calculated against the dim weight rather than the scale weight, and the higher figure shows up in the base rate itself. Dim weight affects the base, not a separate subline, so it falls into the base-shipping bucket; the allocation work doesn't change.

Multi-package orders: allocate to consignment, then to SKU

When one order ships in multiple parcels, the per-order column is built from the bottom up. Consignment is the unit the carrier bills; package is usually the same as consignment but occasionally subdivides; SKU sits below both, requiring an apportionment step within each package. The cascade runs:

  • consignment_cost = base_rate + allocated_surcharges (the per-tracking-number figure produced by the prior section)
  • order_cost = sum(consignment_cost) for all consignments where order_id matches
  • package_cost = consignment_cost / packages_in_consignment for the rare case where one tracking number bills multiple physical parcels (most carriers issue one tracking number per package, so this collapses to package_cost = consignment_cost in nearly all cases)
  • sku_shipping_cost = package_cost * (sku_weight / total_package_weight) for weight-based apportionment, or package_cost * (sku_units / total_package_units) for unit-count apportionment

Per-SKU apportionment is the step that needs a method choice. The two defensible methods are by weight and by unit count. Weight-based is more accurate when SKUs in a package have meaningfully different weights — a winter coat at 1.2 kg next to a t-shirt at 0.18 kg is a 6.7-to-1 weight ratio, and apportioning the package's shipping cost by units would assign equal cost to both even though the coat is doing nearly all the work pushing the package into the next billing-weight band. Unit-count apportionment is simpler, requires no weight column on the SKU master, and is sufficient where SKUs in a package are roughly homogeneous in weight (a beauty brand shipping skincare bundles, a supplements brand shipping fixed-size bottles).

The cascade demands three pieces of input data, and the data-availability question is what determines whether per-SKU costing is realistic for a given brand. The first is the tracking-number-to-order join from the prior section. The second is per-SKU weight on the SKU master, if weight-based apportionment is the chosen method. The third is the per-package SKU manifest — which SKUs went into which physical package when an order shipped in two or more parcels. The third is the one most brands lack.

Most warehouse management systems record what shipped on an order — the line items the order contained — but not which SKUs went into which physical package once the picker split the order. The shipping-label record carries weight, dimensions, and a tracking number per package, but rarely a per-package contents list. A brand without that manifest data cannot strictly compute per-SKU shipping cost for multi-package orders; it can compute per-order shipping cost, which still feeds order-level contribution margin, just not SKU-level.

The realistic fallback when the manifest is incomplete is a cross-package average: divide the total order cost across the order's SKUs by weight share or unit-count share for the whole order, ignoring which physical package each went in. This produces SKU-level numbers that are wrong in detail (a SKU that actually shipped alone in a small parcel gets allocated some of the cost of a separate large parcel in the same order) but right in aggregate (total per-SKU costs sum to the order total). Tag the resulting figures so margin analysis knows they're cross-package estimates rather than manifest-derived. For most brands without WMS-level package detail, this is the right answer; it's worse than per-package apportionment and better than abandoning the SKU dimension entirely.

The reverse case is B2B consolidations from the prior section's breakage list — one tracking number, multiple orders. Here the cascade reverses: split the consignment cost across the orders within it before the per-order rollup begins. Apportion by weight share or unit-count share within the consignment using the same method choice. Once the per-order figure is established for each of the orders in the consolidation, the rest of the cascade runs as normal.


The 3PL passthrough opacity problem and how to work around it

Everything in the prior sections assumes the brand has the carrier invoice in hand — line by line, with tracking numbers and surcharge sublines exposed. That assumption breaks the moment fulfilment goes through a 3PL. When the 3PL is the carrier-of-record for billing purposes, the carrier invoice is theirs, not yours, and the line you see on the 3PL's monthly invoice is at best a passthrough of the carrier figure and at worst a flat fee with no underlying detail at all.

Two billing patterns recur. Passthrough means the 3PL passes the carrier's actual cost through, line by line or in aggregate, sometimes with a markup line broken out separately, sometimes baked into the rate. Fulfilment-platform 3PLs (the ones with order-management software and dashboards) tend to bill this way, because their cost stack is itself a passthrough of the carriers they buy from. Bundled means the 3PL quotes a flat per-order or per-zone shipping fee that has no line-by-line tie back to a carrier rate — you pay the 3PL the bundled figure, and the 3PL's margin on the carrier rate is absorbed into the bundle. Smaller and more specialised 3PLs, particularly those running their own fleet for last-mile, tend to bill this way.

The workarounds run in order of fidelity, from highest to lowest.

Request the per-shipment detail file. Almost every 3PL maintains a shipment-level manifest internally, even when their invoice doesn't expose it — they have to, because the carrier billed them at that level. The manifest carries tracking number, carrier, base rate, surcharges, and total per consignment. Many 3PLs provide it on request as a monthly CSV, sometimes via an API or SFTP feed, sometimes on email request. Ask first; this is the highest-fidelity path and gets you back to the ground the prior sections work on. The per-shipment file is the input the rest of the methodology in this article expects.

Reconcile the file's total against the 3PL invoice line. Once the detail file is in hand, sum it and check it ties to the 3PL's invoice line within rounding tolerance, the same control the next section formalises for direct carrier invoices. Material deltas — anything beyond a euro or two — signal the file is incomplete (orders missing, surcharges aggregated up), the markup isn't disclosed (the file shows carrier cost but the invoice shows carrier cost plus an undisclosed margin), or both. The reconciliation is where the markup, if any, becomes visible.

Apply a documented allocation method when detail is genuinely unavailable. When the 3PL bills bundled and refuses or cannot produce per-shipment detail, allocation has to fall back to apportionment across the orders that month, using the same weight-share or unit-count-share rules from the multi-package section. The bundled line is divided across the orders that shipped in the period it covers. The output is lower-fidelity than per-shipment allocation, and the spreadsheet should mark it as such — tag those rows "3PL-bundled" so contribution-margin analysis can flag them as estimates rather than measured cost. Document the policy in a notes field on the spreadsheet so the audit trail is clear and so next month's analyst applies the same method.

Escalate when the delta is material. Set a tolerance threshold for the per-shipment-file reconciliation, commonly around 5% of the 3PL line, above which an unexplained delta gets escalated to the 3PL account manager. Below the threshold, document and accept. Above it, the right move is a conversation with the 3PL — usually the answer is either an undisclosed markup (negotiable, sometimes) or an aggregation error in their billing run (refundable, often).

The 3PL passthrough opacity question is part of a wider 3PL-cost-control conversation that includes line items beyond shipping. Handling fees, storage charges, value-added services, special-project lines, and account fees all show up on the same monthly 3PL invoice and need their own reconciliation discipline. The shipping-line allocation in this article is one slice; for the full scope of what to look for across a 3PL invoice, the 3PL invoice reconciliation across storage, handling, and shipping lines walkthrough covers the broader workflow. Some of the noisier line items — the ones that show up under labels like "miscellaneous" or "service surcharge" — are worth looking at on their own, and the hidden 3PL fee categories and benchmarks reference is the right place for that.

A closing point on honesty. When the 3PL bills bundled and refuses detail, per-SKU shipping fidelity for those orders is genuinely lost, and no allocation method recovers it — manufacturing precision the source data doesn't support only hides the gap. Margin analysis derived from estimated rows is still useful; it just isn't the same as margin analysis derived from measured rows, and the spreadsheet should be honest about which is which.


Per-channel rollup: Amazon, Shopify, eBay, Etsy

The per-order shipping cost column was built to join. Every order in the order/sale/COGS table carries a channel field — the source system the sale came through — and the per-order shipping cost joins on order ID with the channel field riding alongside. Aggregate by channel and per-channel shipping cost falls out; subtract it, along with product cost and the channel's own fees, and per-channel contribution margin falls out after it. The column is the same for every channel. What changes from channel to channel is where the number comes from.

For Shopify direct, it comes from exactly the methodology built so far. The brand prints labels on its own carrier accounts, the carrier invoices arrive monthly, and the tracking-number join plus surcharge allocation produces the per-order figure. Amazon FBM (Fulfilled by Merchant) sits in the same bucket: the brand ships against its own carrier accounts and the order is just another row in the carrier invoice, tagged to the Amazon channel rather than the Shopify one.

Amazon FBA breaks the pattern. Under FBA, Amazon picks, packs, and ships from its own fulfilment network, and there is no carrier invoice line to allocate — the cost arrives as a per-unit FBA fulfilment fee that bundles pick, pack, and the carrier leg into a single charge tied to the product's size and weight band. That fee doesn't show up on a DHL or DPD invoice; it shows up in the Amazon FBA fee report. For FBA orders, the per-order shipping cost column is sourced from that report, not from a carrier invoice, and the methodology shifts from allocation to lookup: pull the fulfilment fee per unit from the FBA fee schedule, multiply by units, write it into the same column. The column header is identical; the data feed behind it is a different file.

eBay and Etsy fall between the two. A brand may run its own carrier accounts for marketplace orders, in which case the orders flow through the carrier invoice exactly like Shopify direct and pick up the channel tag at the join. Or it may buy shipping through the platform's own label service (eBay labels, Etsy's purchased shipping), in which case the platform bills the postage and that platform-level shipping line needs reconciling against the carrier invoice the platform draws on — the same tie-out discipline, one layer removed. Multi-region marketplace programmes complicate it further: Amazon's pan-EU programme and eBay's Global Shipping Programme take over the cross-border leg entirely, replacing some or all of the brand's carrier-direct lines with a programme fee that, like the FBA fee, comes from the marketplace's reporting rather than a carrier PDF.

The pivot this enables is the point of the whole exercise. With per-order shipping cost populated from whichever source each channel demands, and product cost and channel fees on the same table, the contribution-margin view pivots on the channel column and breaks down to the SKU within it. That is the shipping cost contribution margin ecommerce SKU analysis brands reach for and rarely have: a SKU that breaks even shipping direct on Shopify can lose money on Amazon FBA once the per-unit fulfilment fee lands, because the FBA fee for an awkwardly-sized item often exceeds what the brand pays its own carrier for the same parcel. Without per-channel shipping cost on the SKU line, that loss is invisible — it hides inside a blended margin that averages the profitable Shopify units against the loss-making FBA ones.

One boundary keeps the column clean. Marketplace selling fees — Amazon referral fees, eBay final value fees, Etsy transaction fees — are not shipping cost and don't belong in this column. They join the same table on order ID and they matter enormously to channel margin, but they live in their own classification as a separate cost line. Folding them into shipping cost double-classifies them and corrupts both the shipping-cost pivot and the fee pivot. Keep shipping cost as shipping cost; let the referral and final-value fees sit in their own column and join on the same key.


Pan-EU worked example: five carriers, one per-order column

Take a mid-size brand selling into Germany, France, the UK, Italy, Spain, and the Benelux. The carrier mix follows the destinations: DHL for German domestic, DPD for France and the UK, GLS across the Benelux, Bartolini in Italy, SEUR in Spain. Five carriers, five invoices a month, five formats. Two of them arrive as PDF only; three include a CSV alongside the PDF. The job is to turn that stack into one per-order shipping cost column.

The target schema is a single flat table, one row per consignment, that rolls up to the order. The columns:

  • Order ID — the join key back to the order/sale/COGS table.
  • Shipment ID — binds the order to its consignments.
  • Tracking Number — one row per tracking number, so a multi-package order spans multiple rows.
  • Carrier — DHL, DPD, GLS, Bartolini, SEUR.
  • Destination Country — the dimension that explains most of the cost variance across an EU book.
  • Channel — Shopify, Amazon FBM, eBay, Etsy, carried through for the per-channel pivot.
  • Base Shipping Cost — the base rate off the carrier line.
  • Fuel/Peak Surcharge Allocated — proportional to base, by the formula from the allocation section.
  • Flat Surcharges Allocated — residential, oversize, address correction, DAS, attached to the triggering consignment.
  • Toll Surcharge Allocated — Maut or péage, proportional to base across the routed consignments.
  • Total Per-Consignment Cost — base plus all allocated surcharges.
  • Order-Level TotalSUMIFS of per-consignment cost across every row sharing the order ID.
  • Reconciliation Flag — set when the carrier-level tie-out in the next section fails for that consignment's invoice.

The monthly process runs the same way every cycle. Pull the order and shipment data from the ecommerce platform and label layer, every tracking number per order. Pull the five carrier invoices into the structured table above. Run the join on tracking number. Apply the surcharge allocation rules per carrier — fuel proportional, residential flat, toll proportional across routed segments. Run the per-consignment-to-order rollup with SUMIFS. Where SKU-level cost is needed, run the multi-package cascade on top. None of that is conceptually hard once the data is in the table. The whole exercise stalls at one step, and it stalls every month at the same step: getting five heterogeneous carrier invoices into the structured table in the first place.

That is the data-entry bottleneck, and at multi-carrier scale it dominates the analyst's cycle. Five formats means five layouts to read. Two PDF-only invoices mean no CSV to import — the figures have to come off the page. The surcharge naming is inconsistent across carriers for the same concept: the fuel line reads Treibstoffzuschlag on the DHL invoice, surcharge gazole on the DPD France invoice, Kraftstoffzuschlag on the GLS invoice, three labels for one allocation rule. An analyst keying this by hand spends the bulk of the month transcribing PDFs into a spreadsheet and the remainder — the part that actually requires judgment — applying allocation rules. The time is inverted. The mechanical step eats the cycle; the analytical step gets the scraps.

This is the step worth automating, and it is a narrow, well-defined step: heterogeneous carrier-invoice PDFs in, one structured per-consignment table out. Automated carrier-invoice data extraction at multi-carrier scale is exactly that conversion. You upload the month's carrier invoices — the DHL, DPD, GLS, and Bartolini PDFs together, mixed formats in one batch of up to several thousand files — and describe the columns you want in a single prompt: tracking number, base rate, each surcharge subline, destination, total, one row per consignment. The extraction reads native and scanned PDFs alike and handles the native-language surcharge labels directly, consolidating Treibstoffzuschlag, surcharge gazole, and Kraftstoffzuschlag into the one fuel column you named regardless of which carrier or language they came off, with every row carrying a reference back to its source file and page for verification. General-purpose AI tools can pull a handful of invoices in a chat, but the per-order column is a monthly production job across thousands of consignments, and the value is in the same prompt returning the same structured schema across every carrier in the batch, every month. The output is the structured table the schema above describes. The allocation rules, the rollup, the reconciliation, the margin pivots — that analysis is yours to build on top of it. Extraction is the data-entry step that clears the way for it.


The reconciliation control: per-order costs must tie to the carrier invoice

The control that tells you the allocation is complete is a tie-out: the sum of per-consignment costs — base plus every allocated surcharge — for one carrier in one month must equal that carrier's invoice total for the month, within rounding tolerance. Run it per carrier, per month, before the per-order column feeds anything downstream. If it ties, the column is trustworthy. If it doesn't, the allocation is missing cost somewhere and every margin number built on it is wrong by the size of the gap.

Tolerance is small and carrier-specific. On a five-figure monthly invoice, a few euros of difference is rounding noise; expressed as a percentage, a threshold under 0.1 percent is typical. The reason it isn't zero is that carriers round at different points — some round each line, some round only the invoice total — so the sum of rounded line figures and the rounded invoice total can differ by cents per line that accumulate across hundreds of consignments. Set the threshold per carrier to absorb that, and no wider.

The check itself is one absolute-difference comparison:

tolerance_check = ABS(SUM(per_consignment_cost WHERE carrier=X AND month=Y) - carrier_invoice_total_X_Y) <= tolerance_threshold

Inside the threshold, the allocation is complete. Outside it, something is missing or misallocated, and the delta's size and sign usually point to which.

A failed tie-out has a short list of usual causes, each with its own fix:

  • A missed surcharge line. A surcharge subline that never got allocated to any consignment — the sum comes up short by exactly that line. Find it on the invoice and route it through the right allocation rule.
  • Fuel-allocation rounding accumulating. Proportional allocation distributes a total across lines, and the rounded per-line shares can sum to slightly less or more than the total. A residual-assignment step (push the rounding remainder onto the largest line) clears it.
  • A credit memo not reflected. The carrier issued a credit — a void refund, a service-failure adjustment — that's in the invoice total but not in your per-consignment rows. The sum overshoots. Add the credit.
  • An account-level line not tied to a tracking number. Weekly account fees, insurance, surcharge-on-surcharge administrative lines that don't belong to any one consignment. These need an allocation policy of their own (usually proportional to base across the invoice) rather than being left out.
  • Voids the carrier didn't credit. A label voided on your side but still billed on theirs sits in the invoice total with no shipment behind it. Until the carrier credits it, it's a real billed cost; allocate it or carry it as an open item, but account for it.

That last category is where this reconciliation touches the billing audit. A delta that resolves only after the carrier applies a credit was never an allocation error on your side — it was a carrier billing error that the tie-out surfaced. The margin-analysis control and the billing-audit workflow share the same monthly invoice and the same tie-out arithmetic; here it confirms the per-order column is complete, and on the audit side the unresolved delta becomes a dispute. Treat the reconciliation as the monthly gate: it runs after the column is built and before the column is trusted, and a passed tie-out per carrier is what licenses the SKU and channel pivots to run on real numbers.


Where this article ends and adjacent workflows begin

The scope here is bounded and worth restating plainly. This is per-order shipping cost allocation across a multi-carrier portfolio: it starts from extracted carrier-invoice lines, joins them to orders through the tracking number, allocates surcharges by type, cascades to SKU where the manifest allows, rolls up by channel, and closes with a monthly carrier tie-out. It ends at a per-order, per-SKU, per-channel cost column. It sits downstream of carrier-invoice extraction and upstream of contribution-margin analysis. Several adjacent workflows touch the same invoices and the same orders, and it helps to know where each one picks up.

Billing audit runs in parallel on the same invoice. The audit asks where the carrier got the bill wrong; allocation asks what each order cost to ship. Same source document, different question, and the monthly tie-out is where the two briefly meet — an unresolved delta that the carrier later credits was an audit finding all along. Platform-specific shipping reconciliation is a layer above the carrier: when a brand ships through an aggregator like Sendcloud rather than holding the carrier accounts directly, the platform invoice has to be broken out by underlying carrier before the allocation rules in this article apply, and that line-by-line breakout is its own exercise — the Sendcloud invoice reconciliation line by line walkthrough covers it.

Three more sit at the edges. 3PL-side cost reconciliation is broader in scope than shipping alone — handling, storage, and value-added-service lines share the 3PL invoice with the shipping line and need their own treatment beyond the passthrough problem covered earlier. Multi-3PL benchmarking is a different question again: where this article handles the brand-direct-with-carriers case, comparing fulfilment economics across providers is the multi-3PL pan-EU invoice consolidation across providers case, which asks which 3PL is cheaper rather than what a given order cost. And franchise-carrier extraction is a sub-case that runs before any of this: when the carrier of record is hidden behind a consolidator's invoice, as with Mail Boxes Etc franchise invoice extraction across EU countries, the underlying carrier and consignment detail has to be recovered from the franchise invoice before the allocation rules have anything to operate on.

What you leave with is a single column: per-order shipping cost on the order/sale/COGS table, sourced from allocated carrier-invoice lines, reconciled monthly per carrier within tolerance, and joinable to the SKU and channel dimensions. That column is what turns true contribution margin per order, per SKU, and per channel from a quarterly reconstruction into a pivot-table query against numbers you can stand behind.

Extract invoice data to Excel with natural language prompts

Upload your invoices, describe what you need in plain language, and download clean, structured spreadsheets. No templates, no complex configuration.

Exceptional accuracy on financial documents
1–8 seconds per page with parallel processing
50 free pages every month — no subscription
Any document layout, language, or scan quality
Native Excel types — numbers, dates, currencies
Files encrypted and auto-deleted within 24 hours
Continue Reading