All example use cases

Example use case, not a customer story

How an in-house developer team would connect QWTN to its own ERP

A waste company with its own ERP and a small developer team. They want jobs in, signed notes and payments back, and ESG figures in their dashboard.

  • 2developers
  • 1ERP to connect
  • 120requests a minute, per key
  • £69Fleet, a month

An invented business. The team, fleet and volume figures are assumptions for this example, not results from a customer.

Who they are and what goes wrong

A waste company with an in-house ERP that holds customers, jobs and the general ledger. A small developer team wants the ERP to stay the system of record for money and customers, and QWTN to handle the transfer notes, signatures and driver side.

Every job is typed twice, once in the ERP and once on a note. Signed notes and payments are chased by phone and keyed back in.

The sustainability team asks for recycling and carbon figures each quarter, and someone exports and merges files to produce them.

Business
Waste collection with an in-house ERP
Team
Two developers and an operations lead
Goal
No re-keying between the ERP and QWTN
Stack
Node and a data warehouse, as an example
Starting point
CSV files moved by hand every night

How the integration runs

  1. Job bookedDraft the note from the ERPThe ERP creates a draft note with the waste description, EWC code, client and weight. The job id is the idempotency key, so a retry after a timeout never makes a second note.
  2. Before collectionAsk for a signatureThe ERP asks the producer to sign by emailed link. This call uses the app API with a full-access key.
  3. SignedThe ERP is toldQWTN sends a signed note.signed event and the ERP marks the job documented. A collection.completed event follows when the driver finishes.
  4. PaidPayments come backQWTN raises invoices from billing runs. When one is paid, by payment link or by hand, an invoice.paid event lets the ERP post to the ledger.
  5. Each quarterPull the ESG figuresThe dashboard asks for recycling rate, landfill diversion and estimated carbon for a period, for all customers or one.
Example flow. Webhook events are signed, and the ERP replies with a 2xx to confirm.

What they use

  • Typed REST endpoints

    Notes, clients, client sites, receiving sites, collections, containers and weighbridge tickets, with read and write scopes. Bulk create takes up to 100 records a call.

    Endpoints
  • Signed webhooks

    Eight event types, HMAC-signed, retried with growing delays and replayable from the delivery log.

    Webhooks
  • Idempotency keys

    Send a key on every POST and PATCH. A retry within 24 hours returns the original response.

    Idempotency
  • The app API

    Everything the web app can do is reachable under /api/v1/app with a full-access key, run as the person who made the key.

    The app API
  • Audit events

    Read the change history through /api/v1/audit-events with an audit:read key, filtered by record, client, action or source.

    Audit trail endpoint
  • OpenAPI 3.1

    A generated spec to import into Postman, Insomnia or a code generator.

    OpenAPI JSON

The calls in code

Real endpoints from the developer docs. Keys, ids and addresses are placeholders.

Create a transfer note from a booked job

A key with the notes:write scope is enough. The idempotency key stops a retry creating a second note.

const res = await fetch('https://qwtn.co.uk/api/v1/notes', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.QWTN_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'erp-job-' + job.id,
  },
  body: JSON.stringify({
    waste_description: 'Mixed construction and demolition waste',
    ewc_codes: ['17 09 04'],
    client_id: job.qwtnClientId,
    weight_kg: 1200,
    transfer_date: '2026-10-14',
  }),
})
const note = await res.json()
// the new draft note: note.id, note.reference, note.status === 'draft'

// Ask the producer to sign. This is an app API call and needs a full-access key.
await fetch('https://qwtn.co.uk/api/v1/app/notes/' + note.id + '/request-signature', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.QWTN_FULL_ACCESS_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    party: 'transferor',
    recipientEmail: 'site@client.co.uk',
    recipientName: 'Sam Lee',
  }),
})

Receive and verify a webhook

Check the HMAC of the timestamp and raw body, reject anything older than five minutes, dedupe on the event id and reply fast. Add the endpoint under Settings, Webhooks.

import crypto from 'node:crypto'
import express from 'express'

const app = express()

app.post('/webhooks/qwtn', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('QWTN-Signature') ?? ''
  const t = Number(/t=(\d+)/.exec(header)?.[1])
  const expected = crypto
    .createHmac('sha256', process.env.QWTN_WEBHOOK_SECRET)
    .update(t + '.' + req.body)
    .digest('hex')

  const fresh = Math.abs(Date.now() / 1000 - t) < 300
  const valid = [...header.matchAll(/v1=([0-9a-f]{64})/g)].some(([, sig]) =>
    crypto.timingSafeEqual(Buffer.from(sig, 'hex'), Buffer.from(expected, 'hex')))
  if (!fresh || !valid) return res.status(400).end()

  const event = JSON.parse(req.body.toString())
  if (alreadyProcessed(event.id)) return res.sendStatus(204)

  switch (event.type) {
    case 'note.signed':          markJobDocumented(event.data.object); break
    case 'collection.completed': releaseForBilling(event.data.object); break
    case 'invoice.paid':         postToLedger(event.data.object); break
  }
  res.sendStatus(204)
})

Pull ESG figures for a period

The report runs as the person who made the key, so that person needs permission to view reports. Add customer=CLIENT_ID for one customer, or format=csv, pdf or returns for a file.

const url = new URL('https://qwtn.co.uk/api/v1/app/reports/esg')
url.searchParams.set('from', '2026-07-01')
url.searchParams.set('to', '2026-09-30')

const r = await fetch(url, {
  headers: { Authorization: 'Bearer ' + process.env.QWTN_FULL_ACCESS_KEY },
})
const { summary } = await r.json()

// summary.totalTonnes, summary.recyclingRate, summary.landfillDiversion,
// summary.carbonKg (an estimate, with the method in the full report)

Copy the change history each night

A key with the audit:read scope can read the audit trail. Add source=api to see only changes made through the API, and page with next_cursor.

curl "https://qwtn.co.uk/api/v1/audit-events?created_after=2026-10-14T00:00:00Z&limit=100" \
  -H "Authorization: Bearer $QWTN_AUDIT_KEY"

The plan that fits

Fleet, because the API and webhooks start there

REST API access and webhooks are Fleet features. Scoped keys let the team give each job the least it needs: notes:write for the booking call, audit:read for the warehouse copy, and one full-access key kept in a secrets store for the app API.

What would change it. A team that only needs notes, signatures and scheduling, without the API or webhooks, could use Carrier instead. A 30-day trial is available on paid plans.

Compare every plan
  • Fleet£69 a month, or £690 a year
  • Unlimitedpeople on this plan

No VAT charged. QWTN is not VAT-registered, so the price you see is the price you pay.

What to check first

The plan and feature details that matter most for this example.

  • The limit is 120 requests a minute per key. Over it you get 429 with a Retry-After header, so page through lists rather than fetching records one by one.
  • The app API returns the app’s own JSON. It is stable in practice but not versioned like the typed endpoints, so prefer a typed endpoint where one exists.
  • Webhooks are at-least-once and order is not guaranteed. Dedupe on the event id and fetch the record if you need its latest state.

Make your first API call

Create a key under Settings, API access, or start with an account and the demo.

Example use cases are illustrations, not customer stories.