Tour compensation object

May 7, 2026

We have introduced a structured tour compensation object as the single source of truth for what a driver is paid out for a tour. It consolidates base earnings, bonuses, and tips into one auto-totalled view — exposed via a dedicated API, surfaced to drivers on tour cards via two new configurable labels, and observable for server-to-server integrations through a dedicated webhook.

The new object is purpose-built for the kind of compensation logic 3PL operations need: per-component editing, bonus components with self-provided keys, and a structured breakdown that is accessible to drivers. It is the foundation to build robust payroll reporting on top of, and the place to express anything richer than a flat payout number going forward.

The legacy single-amount earnings attribute it supersedes is now deprecated and will be removed on 2026-11-06.

Where to start

The full surface and its conventions live under the Tour Compensation tag — the tag overview goes deeper than this announcement on currency rules, idempotency, and how concurrency is handled. Start there if you’re integrating.

What’s new

A structured object on every tour — read the current breakdown via Get tour compensation. Three slots, all expressed in a single tour-wide currency:

total_amount is recomputed automatically whenever any component changes, and labels + metadata are kept on each component for human-readable display and audit context.

Bonus-key uniqueness as a built-in safeguard. A bonus key is enforced unique per tour. This is more than a data-model nicety: it means an accidental retry, a duplicated job, or a re-fired campaign event can’t double up the same incentive on the same tour. Use this to your benefit — pick a stable, descriptive key (completion_q1, peak_hours, late_night) for anything that should never accidentally stack, and reach for distinct keys when you genuinely want multiple incentives to coexist on the same tour.

Currency-aware writes. Every write must include currency, and the API rejects any value that doesn’t match the tour’s resolved currency (the service area’s local_currency, with fallback to the tenant-wide base_currency). This keeps tours that span service areas configured in different currencies from silently miscalculating.

Concurrency-safe writes. Per-component writes are idempotent and last-write-wins. Concurrent edits on the same component resolve via a 409 Conflict on the losing caller — read back, then retry. Writes to different components don’t contend and proceed in parallel.

A debounced webhook. Subscribe to tour.compensation_updated to react to changes. After a compensation component changes, we wait for a 120-second quiet window before firing — any further change on the same tour resets the timer. When the timer expires, a single webhook fires with the current state. Bursts of writes (e.g. setting base earnings + two bonuses in quick succession) collapse into one delivery that reflects the final state, not each intermediate step. The payload excludes labels and metadata so subscribers can act on amounts without round-tripping to our backend.

Driver-app visibility. Two new configurable labels — Compensation and Tips — surface the breakdown directly on the driver’s tour cards. The Compensation label shows the headline payout (base earnings plus bonuses, with tips excluded so the number stays stable across the tour); the Tips label surfaces the tip row separately when one is set. Both ship with sensible default prefix translations and are configurable on both the off-route and active-tour displays via Update Tour Display Configuration.

Migrating off the legacy earnings surface

The flat earnings attribute on tours, the ⚠️ Update driver earnings endpoint, and the ⚠️ tour.earnings_updated webhook are now deprecated and will be removed on 2026-11-06. The new compensation object is the migration target — see the deprecation announcement for the side-by-side guide. There’s no behavioural change to the legacy surface within the deprecation window; switch over at your own pace.