Skip to content

Demo: This is a demo template by Ruji Labs. The business, people, products, prices and reviews on this page are fictional and do not represent any real company.

zahin ooi
menu

Webhook delivery for Tidewire

Rebuilt how shipment events reach customer systems, with fair queues and honest retries. Tidewire: freight visibility platform for shippers across ASEAN.

99.98%

events delivered within 60 seconds

Client
Tidewire
Role
Senior engineer, platform team
Duration
5 months
Shipped
2024.05, v2.0
Stack
Rust, PostgreSQL, SQS, TypeScript

Problem

Tidewire tells shippers where their containers are by sending webhooks: arrived at port, cleared customs, out for delivery. One large customer's endpoint went down for an afternoon, retries piled up in a shared queue, and every other customer's events arrived four hours late.

Customers had no way to see what was sent, what failed or why. Every incident became a support thread with screenshots of logs.

Constraints

3 that shaped the design

  • About 40 million events a day, spiking around vessel arrivals.
  • Customer endpoints range from serverless functions to a single PHP box in a warehouse office.
  • Events for one shipment must arrive in order.

Architecture

scroll sideways to see all of it

Each endpoint gets its own lane. A slow customer slows only itself.
events40M / dayfan-outby subscriptionlane: endpoint Ahealthylane: endpoint Bbacking offlane: endpoint Chealthyattempt logcustomer-visible

boxes are servicesdouble top rule is a storedashed is async or advisorygreen is what this project built

Code

excerpt, lightly trimmed

Full jitter keeps a recovering endpoint from being hit by every retry in the same second.

delivery/backoff.rsrust
/// Delay before attempt n (1-based). Caps at 30 minutes, gives up after 24 hours.pub fn next_delay(attempt: u32, rng: &mut impl Rng) -> Option<Duration> {    if attempt > 18 {        return None; // dead-letter, customer sees it in the dashboard    }    let cap = 1_800_000u64; // 30 min in ms    let base = 500u64.saturating_mul(1 << attempt.min(16));    let ceiling = base.min(cap);    Some(Duration::from_millis(rng.gen_range(0..=ceiling)))}

Result

measured, not estimated

delivered within 60 seconds
99.98%
delay caused to other customers by one outagewas 240 min
0 min
webhook support tickets
-62%
replay from the customer dashboard
1-click

The attempt log became a product feature with its own page in the customer console, and it is now the first thing sales demos.

Demo only

This button is part of a demo template. Nothing was sent, booked or charged, and there is no real business behind this page.

Want a page like this for your business?

QR code: Darus Ishak, Ruji Labs contact cardScan or tap to get the contact card of Darus Ishak, Ruji Labs.Open contact card ↗