Developers · Core concepts

Webhooks

Signed, retried event notifications for passports, models and supplier submissions, with signature verification in Node.js, Python, PHP, Java, C#, Go and the TypeScript SDK.

Version
v1.0.0
Base URL
https://dppapi-production.up.railway.app/v1
Format
JSON · OpenAPI 3.1
FeedbackLast updated

Bu makale İngilizce olarak mevcuttur.

On this pageSet up an endpoint

Instead of polling, let us tell you. Every change worth knowing about becomes a signed HTTPS POST to your endpoint, retried until it gets through.

Set up an endpoint

Register an endpoint in the app under Organisation → API → Webhooks, or over the API with `POST /webhooks`. Choose the events it receives — none selected means all; `GET /webhooks/event-types` lists them. Copy its signing secret (whsec_…) into your secret store, then send a signed ping with `POST /webhooks/{id}/test` — or from the app — and check your receiver answers 2xx.

  • The URL must use https and resolve to a public address; private, loopback and link-local addresses are refused.
  • Credentials in the URL are not allowed — the signature is the authentication.

Over the API you can also update or pause an endpoint (enabled: false), rotate its secret, read its delivery log and resend a delivery. Endpoints are paused rather than deleted over the API, so their history survives.

Events

TypeWhendata contains
passport.createdA battery and its draft passport were created (once per battery in a batch).passport
passport.publishedA passport was published.passport
passport.status_changedA passport’s status changed (PATCH /passports/{id}/status).passport, status; forked (passportId, uniqueIdentifier of the new linked passport) for a repurpose, re-use or remanufacture — none for WASTE
passport.recycledA passport ceased because the battery was recycled.passport
passport.event_recordedDynamic data was recorded.passport, event (id, eventType, payload, recordedAt)
model.createdA model was created.model
model.updatedA model was changed.model
document.uploadedA document was uploaded to a model.document (id, modelId, name, type, point, createdAt)
supplier.submittedA supplier answered a data request — values or a file.request (id, supplierName, modelId, kind), and fields or file

passport is { id, uniqueIdentifier, serialNumber, itemId, modelId, status, lifecycleState, publishedAt, publicUrl }; model is { id, name, category, capacityKwh, chemistry, updatedAt }. Payloads are deliberately small: fetch the full record with its id when you need more. The test event is ping, with data.message. Each event type is also described in the webhooks section of the OpenAPI document.

The delivery

POST to your endpoint
POST /hooks/dpp HTTP/1.1Content-Type: application/jsonUser-Agent: Collecta-DPP-Webhooks/1.0DPP-Event: passport.publishedDPP-Event-Id: evt_5f0c1d2e3a4b4c6d8e9f0a1b2c3d4e5fDPP-Delivery: cm1qf3g4h0060l70fm5n6b7v8DPP-Signature: t=1799920931,v1=6c1f0e…9b2a
Body
{"id": "evt_5f0c1d2e3a4b4c6d8e9f0a1b2c3d4e5f","type": "passport.published","created_at": "2027-01-15T10:02:11.214Z","data": {"passport": {"id": "cm1q8y0bq0006l70fx9y8z7w6","uniqueIdentifier": "C-7KQ2M9XHT4R","serialNumber": "C48-2027-0001","itemId": "cm1q8y0bq0005l70fq2r3t4u5","modelId": "cm1q8x2k40003l70f9a8s7d6e","status": "ORIGINAL","lifecycleState": "ACTIVE","publishedAt": "2027-01-15T10:02:11.000Z","publicUrl": "https://dppapi-production.up.railway.app/p/C-7KQ2M9XHT4R"}}}

Verify the signature

DPP-Signature is t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of the string "<t>.<raw body>" keyed with your endpoint secret. To verify a delivery:

  1. 1Read the raw request body, byte for byte. Parsing and re-serialising JSON changes it and breaks the signature.
  2. 2Split the header on , and each part on the first =; take t and v1.
  3. 3Reject the delivery if t is more than five minutes from your clock — that blocks replays of captured requests.
  4. 4Compute HMAC-SHA256 of t + "." + body with the secret, as hex, and compare it to v1 in constant time.
// Node.js 18+, no dependencies. Verify against the RAW body — not re-serialised JSON.import { createHmac, timingSafeEqual } from 'node:crypto';import { createServer } from 'node:http';const SECRET = process.env.DPP_WEBHOOK_SECRET; // whsec_…const TOLERANCE_SECONDS = 300;export function verifyDppSignature(rawBody, header, secret = SECRET) {const parts = Object.fromEntries(String(header ?? '').split(',').map((pair) => pair.trim().split('=', 2)),);const timestamp = Number(parts.t);if (!Number.isInteger(timestamp) || !parts.v1) return false;if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) return false;const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest();const received = Buffer.from(parts.v1, 'hex');return received.length === expected.length && timingSafeEqual(received, expected);}createServer((req, res) => {const chunks = [];req.on('data', (chunk) => chunks.push(chunk));req.on('end', () => {const raw = Buffer.concat(chunks).toString('utf8');if (!verifyDppSignature(raw, req.headers['dpp-signature'])) {res.writeHead(400).end();return;}const event = JSON.parse(raw);res.writeHead(200).end(); // acknowledge first, then do the workconsole.log(event.id, event.type, event.data);});}).listen(8080);

Responding and retries

  • Answer with any 2xx within 10 seconds. Acknowledge first and do the work afterwards, on a queue.
  • Anything else — another status, a timeout, a connection error, or a redirect (redirects are not followed) — is a failed attempt.
  • Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours. After the sixth attempt the delivery is marked failed.
  • The delivery log in the app shows each attempt, the status and body your endpoint returned, and lets you resend.

At least once, in any order

A delivery can arrive more than once — a retry after your endpoint processed it but timed out — and deliveries can arrive out of order. Make the handler idempotent: store the event id (also in DPP-Event-Id) and skip ids you have seen. Where order matters, compare created_at, or fetch the current state of the record by its id.

Çerez tercihleri

Hangi çerezlerin kullanılacağını siz seçersiniz. Tercihinizi istediğiniz zaman alt bilgideki “Çerez ayarları”ndan değiştirebilirsiniz. Gizlilik politikası

Gerekli

Her zaman açık

Sitenin çalışması için gereklidir: açık/koyu tema seçiminiz ve bu çerez tercihiniz tarayıcınızda saklanır. Sizi izlemek için kullanılmazlar.

Analitik

Google Analytics 4, sayfaların nasıl kullanıldığını istatistiklerle ölçer ve siteyi iyileştirmemize yardımcı olur. Kabul ederseniz çerez bırakır ve ziyaretinizi tam olarak ölçer. Reddederseniz çerez bırakmaz, ancak Google yine de görüntülenen sayfa, tarayıcı türü ve IP adresi gibi teknik veriler alabilir.

_ga, _ga_* · en çok 2 yıl · Google