Skip to Content

Webhooks

A webhook endpoint is a URL of yours that Yona calls when something happens to your organisation’s data: an invoice is registered, a payment is recorded, a received invoice arrives. Each delivery is signed, so you can check it came from Yona.

Endpoints

Create endpoints in the Yona app under Webhooks. Each has a URL, the events it subscribes to, and an environment: an endpoint created on the sandbox only receives sandbox events. The signing secret, whsec_ followed by 43 characters, is shown once.

With an API key you can list and read endpoints, send a test event, read deliveries and ask for a redelivery. Creating, changing, deleting or rotating an endpoint’s secret is done in the app.

OperationPath
List endpointsGET /n/v1/webhook-endpoints
Send a test eventPOST /n/v1/webhook-endpoints/{id}/test
List deliveriesGET /n/v1/webhook-deliveries
RedeliverPOST /n/v1/webhook-deliveries/{id}/redeliver
Event catalogueGET /n/v1/webhook-event-types

Events

The catalogue at GET /n/v1/webhook-event-types lists every event type with the capability a subscription needs and whether your key holds it. Subscribe with exact names or patterns. The events most integrations want are the submission steps on an invoice: queued, handed_off, registered, transmitted, delivered, and the exceptions rejected, parked, resumed and withdrawn, plus invoice.received for invoices other businesses send you.

A delivery

Yona sends an HTTP POST with a JSON body and these headers:

HeaderContent
Yona-Signaturet=<unix seconds>,v1=<hex>; during a secret rotation a second v1= signed with the previous secret is appended
Yona-Event-IdThe event’s id; the same event is never two ids
Yona-Event-TypeThe event type
Yona-Delivery-IdThis delivery’s id
Yona-Delivery-Attempt1 for the first attempt, then 2, 3, …
User-AgentYona-Webhooks/1.0

Answer with any 2xx within a few seconds and do the work afterwards. Deduplicate on Yona-Event-Id: a retry carries the same id.

Verifying the signature

The signature is HMAC-SHA256 over the string t + "." + body, where body is the raw request body, byte for byte, and the key is your endpoint secret. Compare in constant time, and reject timestamps more than five minutes from your clock.

import crypto from 'node:crypto'; import express from 'express'; const app = express(); const SECRET = process.env.YONA_WEBHOOK_SECRET; // whsec_… app.post('/webhooks/yona', express.raw({ type: 'application/json' }), (req, res) => { const header = req.get('Yona-Signature') ?? ''; const parts = Object.fromEntries(header.split(',').map((p) => p.split('='))); const t = Number(parts.t); const given = header.split(',').filter((p) => p.startsWith('v1=')).map((p) => p.slice(3)); if (!t || Math.abs(Date.now() / 1000 - t) > 300) return res.status(400).end(); const expected = crypto.createHmac('sha256', SECRET).update(`${t}.${req.body}`).digest('hex'); const ok = given.some((sig) => sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig, 'hex'), Buffer.from(expected, 'hex'))); if (!ok) return res.status(400).end(); const event = JSON.parse(req.body); res.status(200).end(); // handle event, keyed by req.get('Yona-Event-Id') });

Parse the JSON only after the signature checks. Frameworks that parse bodies before your handler runs change the bytes; use the raw body.

Retries

A delivery that fails, times out or gets a non-2xx answer is retried with growing delays and a little jitter: eight attempts over about 45 hours. A Retry-After on a 429 or 503 replaces the scheduled delay. After 72 hours of consecutive failures and at least 20 failed attempts the endpoint is disabled; fix the receiver and re-enable it in the app. You can also redeliver any delivery by hand.

Treat events as a nudge

A delivery tells you something changed. Read the current state through the API before acting on it, especially for an invoice: the ladder may have moved on since the event was created.