ECG purchase & webhooks

End-to-end prepaid meter purchase with signed callbacks. Use sandbox keys for simulated fulfillment; production keys use live KNOS providers.

Flow

  1. Look up the meter with POST /meters/lookup.
  2. Create a purchase with productId from GET /products (stable codes such as ecg-ecg).
  3. Poll status or wait for webhooks. On success, read token from the completed transaction / callback payload.

Create prepaid purchase

Scopes: transactions:create, ecg:purchase

POSThttps://confident-elephant-309.convex.site/v1/transactions
Request
curl -sS https://confident-elephant-309.convex.site/v1/transactions \  -H "Authorization: Bearer $KEY" \  -H "Content-Type: application/json" \  -d '{    "idempotencyKey": "ecg-order-001",    "product": "ecg",    "productId": "ecg-ecg",    "amount": 50,    "meterNumber": "0503123456",    "meterId": "sbx-meter-1",    "category": "PREPAID",    "phoneNumber": "0241234567"  }'
201 Created
{  "transaction": {    "id": "511260",    "reference": "511260",    "status": "pending",    "kind": "purchase",    "environment": "sandbox"  }}

Callback headers (actual)

KNOS signs the raw request body with HMAC-SHA256 and sends these headers on every delivery:

HTTP headers
X-Knos-Signature: <hex hmac-sha256 of raw body>X-Knos-Timestamp: <unix milliseconds>X-Knos-Delivery-Id: dlv_…X-Knos-Event: transaction.completed
HeaderMeaning
X-Knos-SignatureHMAC-SHA256 hex digest of the exact body bytes
X-Knos-TimestampUnix time in milliseconds when the attempt was signed
X-Knos-Delivery-IdStable delivery id (also in the payload)
X-Knos-EventEvent name, e.g. transaction.completed

Example completed payload

JSON body
{  "id": "dlv_…",  "event": "transaction.completed",  "timestamp": 1710000000000,  "transaction": {    "id": "511260",    "reference": "511260",    "status": "completed",    "amount": 50,    "currency": "GHS",    "externalReference": null,    "environment": "sandbox",    "beneficiary": "0503123456",    "meterNumber": "0503123456",    "category": "PREPAID",    "token": "1234-5678-9012-3456-7890",    "customerMessage": null,    "errorCode": null,    "completedAt": 1710000001000  }}

Field names are KNOS public names only — never upstream vendor identifiers. Sandbox may return a simulated token; meters ending in 0000 force failure for testing.

Verify signature

HMAC-SHA256 hex of the raw body using your webhook signing secret. Compare to X-Knos-Signature.

Node.js
import crypto from "node:crypto"; function verifyKnosSignature(rawBody, signatureHex, secret) {  const expected = crypto    .createHmac("sha256", secret)    .update(rawBody)    .digest("hex");  if (expected.length !== signatureHex.length) return false;  return crypto.timingSafeEqual(    Buffer.from(expected, "utf8"),    Buffer.from(signatureHex, "utf8"),  );} app.post("/hooks/knos", (req, res) => {  const ok = verifyKnosSignature(    req.rawBody,    req.get("X-Knos-Signature"),    process.env.KNOS_WEBHOOK_SECRET,  );  if (!ok) return res.status(401).end();  res.status(200).end();});

Test from the API

POSThttps://confident-elephant-309.convex.site/v1/webhooks/test
Request
curl -sS -X POST https://confident-elephant-309.convex.site/v1/webhooks/test \  -H "Authorization: Bearer $KEY" \  -H "Content-Type: application/json" \  -d '{}'
200 when URL + secret configured
{ "ok": true }

Also see Webhooks overview and ECG & meters.