Browse Developers
Build with OnloView as Markdown

API security and webhooks

Lock API access to your infrastructure, read mandatory audit events, receive Onlo events, and verify webhook signatures against the raw request body.

For
Backend engineers and security owners operating an Onlo integration
Time
20 minutes

Before you start

  • Owner or admin access to Dashboard → Integrations → API
  • A public HTTPS endpoint for webhook delivery

Lock API access to your backend

  1. Step 1

    Confirm HTTPS from production

    Keep Require HTTPS enabled and call `/ping` from the same deployed service that will make API requests.

    Expected resultA `200` response proves the request reached Onlo through HTTPS.

  2. Step 2

    Save your public egress IP

    Enter the exact IPv4 or IPv6 address used by your deployed service. Save the list before enabling it.

    Expected resultThe dashboard shows the saved address count.

  3. Step 3

    Enable and prove the allowlist

    Enable IP allowlisting, call `/ping` from production, then make the same call from a different public IP.

    Expected resultProduction receives `200`; the unlisted source receives `403 ip_not_allowed`.

Read the mandatory audit trail

Every authenticated `/api/v1` request records its request id, key id, operation, status, outcome, optional denial reason, and safe resource ids. The control is always on and has no dashboard toggle.

List audit eventsshell
curl --request GET "$ONLO_API_BASE_URL/audit?per_page=50" \
  --header "Authorization: Bearer $ONLO_API_TOKEN" \
  --header "Accept: application/json"
Responsejson
{
  "type": "audit_log.list",
  "audit_logs": [
    {
      "type": "audit_log",
      "id": "603a3cd8-43be-43fa-97ea-181b97d1afdd",
      "request_id": "977fba8b-1b57-45c4-a965-bb62e6eebd1d",
      "api_key_id": "12137dde-394b-4e30-aa72-b5bf5f5b9671",
      "operation": "conversation.search",
      "http_status": 200,
      "outcome": "allowed",
      "denial_reason": null,
      "conversation_id": null,
      "contact_id": "29894c6a-7187-45cc-af77-8c58a9dd1511",
      "created_at": 1787740200
    }
  ],
  "pages": { "type": "pages", "per_page": 50 }
}

Supported webhook events

Add an endpoint in Dashboard → Integrations → API and select at least one event. Onlo accepts only a public HTTPS URL and rejects private, loopback, local-network, and internal DNS destinations.

A delivery times out after 8 seconds. The dashboard counts attempts, records the latest failure, marks repeated failures as failing, and returns to healthy after a successful response. Automatic retries are not yet part of the contract, so make your receiver highly available.

EventWhen it is sent
`conversation.created`A new WebChat conversation or Conversations API conversation is created. Idempotent API replays do not send it again.
`ticket.created`A ticket is created through Onlo’s supported ticket flows.
`ticket.resolved`A ticket transitions from an open state to closed.
`ai.handoff`An operator explicitly takes over a conversation.
Delivery bodyjson
{
  "id": "evt_2ca441b4611333b9edc63c20ea9655fa",
  "type": "conversation.created",
  "created_at": 1787740200,
  "data": {
    "id": "9a4f18f5-93ac-4476-b87f-6af63c700c64",
    "channel": "api",
    "status": "active"
  }
}

Verify the HMAC signature

Signing is enabled by default. Onlo computes lowercase hex HMAC-SHA256 over `<X-Onlo-Timestamp>.<raw request body>` with the endpoint secret, then sends `X-Onlo-Signature: v1=<hex>`. Verify the raw bytes before parsing JSON.

Also record `X-Onlo-Delivery` and reject timestamps older than your replay window. The example uses five minutes.

HeaderMeaning
`X-Onlo-Event`Event type, identical to the body’s `type`.
`X-Onlo-Delivery`Unique id for this delivery attempt.
`X-Onlo-Timestamp`Unix epoch seconds used in the signature. Present when signing is enabled.
`X-Onlo-Signature``v1=` followed by the lowercase hex digest. Present when signing is enabled.
Node.js verificationtypescript
import crypto from 'node:crypto';

export function verifyOnloWebhook(rawBody: Buffer, headers: Headers, secret: string) {
  const timestamp = headers.get('x-onlo-timestamp');
  const supplied = headers.get('x-onlo-signature')?.replace(/^v1=/, '');
  if (!timestamp || !supplied || !/^\d+$/.test(timestamp)) return false;

  // Reject replayed deliveries before doing any business work.
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.`)
    .update(rawBody)
    .digest('hex');
  const a = Buffer.from(supplied, 'hex');
  const b = Buffer.from(expected, 'hex');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Verify the complete webhook flow

Create a webhook endpoint with signing enabled, then create one API conversation using a new `external_id`.

Expected result

Your endpoint receives one `conversation.created` POST, signature verification succeeds, its `X-Onlo-Delivery` is unique, and the Onlo dashboard increments Deliveries with Healthy status.

If you don't see this
  • No request: confirm the endpoint is public HTTPS, selected for `conversation.created`, and still listed in the dashboard.
  • Signature mismatch: verify against the exact raw bytes and include `<timestamp>.` before the body. Do not reserialize parsed JSON.
  • Degraded or Failing: inspect Last error, return a 2xx within 8 seconds, and trigger a new event. A success returns the endpoint to Healthy.