ontreDeveloper documentation

Ontre API / Webhooks

Send events to Ontre

Connect your backend to Ontre with one signed JSON event per request. This guide covers the project webhook URL, the event shape, and the exact bytes to sign.

JSON over HTTPSHMAC-SHA256One event per request

Use your Ontre environment’s base URL. https://YOUR_ONTRE_HOST is a placeholder. The webhook path comes from the connection’s postTo field.

Overview

An admin creates a connection for a project. Your backend sends signed requests to that connection’s stable URL. Ontre verifies the signature before reading the event and returns 202 after an active connection has stored it.

The URL identifies the connection. The workspace signing key proves that the request came from your backend. Keep the key on the server.

Set up a connection

01Create a project connectionAn admin creates it in Ontre and saves the returned postTo path.
02Reveal the signing keyWait until signingKey is READY, then store the key in your server’s secrets.
03Send an eventSerialize one JSON object, sign that exact body, and POST it with a fresh timestamp.

Project admins can create a connection with POST /api/v1/projects/{projectId}/connections and a JSON body containing displayName. The response includes id, postTo, and signingKey status. The workspace key is available through authenticated GET /api/v1/connections/signing-key when ready.

The creation response also includes a token shown only once. The project URL uses the connection ID, so store the postTo path for delivery.

Request and headers

POSThttps://YOUR_ONTRE_HOST/webhooks/v2/{connectionId}
HeaderValue
Content-Typeapplication/json; charset=utf-8
X-Ontre-TimestampCurrent Unix time in seconds.
X-Ontre-SignatureLowercase hexadecimal HMAC-SHA256 digest of {timestamp}.{rawBody}, using your workspace signing key.

For an older workspace connection created at POST /api/v1/connections, use its returned /webhooks/v1/{connectionToken} path. The headers, payload, and signing process are the same.

Event payload

Send a single JSON object. An events array is rejected.

Example eventJSON
{
  "source": "checkout-api",
  "eventType": "payment.failed",
  "eventId": "evt_2f9a",
  "occurredAt": "2026-09-16T14:05:00Z",
  "correlationId": "order_88f2",
  "attributes": {
    "amount": 4200,
    "currency": "NGN",
    "errorCode": "gateway_timeout"
  }
}
FieldRequiredMeaning
sourceYesYour system’s name, such as checkout-api.
eventTypeYesYour label for the event.
eventIdYesAn ID unique within that source. Keep it unchanged when retrying the same event.
occurredAtNoISO-8601 event time. If missing or unreadable, Ontre uses receipt time. Events more than 30 days old are rejected.
correlationIdNoA shared order or session ID that connects related steps.
attributesNoFree-form JSON object with event details.

Sign each request

Serialize the JSON body first. Compute HMAC-SHA256 over the UTF-8 bytes of the Unix timestamp, one period, and the exact body you will transmit. Put the lowercase hex digest in X-Ontre-Signature.

Signed inputTEXT
{timestamp}.{rawBody}

Use the same timestamp in the header and signed input. Ontre accepts a clock difference of up to five minutes. For every retry, create a new timestamp and signature without changing the event ID. Do not reformat the body after signing.

Node.js example

Set ONTRE_BASE_URL, ONTRE_WEBHOOK_PATH (the returned postTo path), and ONTRE_SIGNING_KEY in your server environment. Use the signing key as its UTF-8 text value.

send-event.mjsNode.js 18+
import { createHmac } from 'node:crypto';

const baseUrl = process.env.ONTRE_BASE_URL;
const webhookPath = process.env.ONTRE_WEBHOOK_PATH;
const signingKey = process.env.ONTRE_SIGNING_KEY;
if (!baseUrl || !webhookPath || !signingKey) {
  throw new Error('Missing Ontre configuration');
}

const body = JSON.stringify({
  source: 'checkout-api',
  eventType: 'payment.failed',
  eventId: 'evt_2f9a',
  occurredAt: new Date().toISOString(),
  correlationId: 'order_88f2',
  attributes: { amount: 4200, currency: 'NGN' }
});
const timestamp = String(Math.floor(Date.now() / 1000));
const signature = createHmac('sha256', signingKey)
  .update(timestamp + '.' + body, 'utf8')
  .digest('hex');

const response = await fetch(new URL(webhookPath, baseUrl), {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json; charset=utf-8',
    'X-Ontre-Timestamp': timestamp,
    'X-Ontre-Signature': signature
  },
  body
});
if (response.status !== 202) {
  throw new Error('Ontre returned ' + response.status + ': ' + await response.text());
}

Generate a new eventId for each business event. A retry keeps that ID and uses a fresh request timestamp and signature.

Responses and retries

StatusMeaningWhat to do
202Stored, duplicate accepted, or valid event ignored while suspended.Mark the event delivered.
400Malformed event or event more than 30 days old.Fix the event before sending again.
401Invalid signature or timestamp outside the five-minute window.Check the key, exact body bytes, and server clock.
404Unknown or disabled connection.Check the URL and connection status.
413Body exceeds 1 MiB.Reduce the event body.
429Rate limited, if returned by your environment.Back off and retry.
5xxTemporary server failure, including a missing signing key.Retry with exponential backoff and jitter.

Queue events until you receive 202. Ontre deduplicates by workspace, source, and eventId. Delivery order does not determine event time; occurredAt does.

Connection lifecycle

An admin can suspend or activate a project connection through authenticated endpoints:

POST/api/v1/projects/{projectId}/connections/{connectionId}/suspend
POST/api/v1/projects/{projectId}/connections/{connectionId}/activate

Both return 204 on success. GET /api/v1/projects/{projectId}/connections lists status and the postTo path. Disabling a connection stops delivery to that URL.

Suspended events are discarded. A valid signed request still returns 202. Ontre does not store those events or replay them after activation.