OnLink
Get started

Your first call

A signed GET /v1/health, then a real quote, in curl, Node and Python.

GET /v1/health is the smallest signed request you can make. It takes no body and no parameters, so it isolates your signing code from everything else: if it returns 200, your signature, key and clock are all correct.

It is still authenticated. There is no unauthenticated route on this API.

Check the unsigned response first

Before you sign anything, see what an unauthenticated request looks like, so you recognise it later:

curl -i https://sandbox.onlink.africa/v1/health
HTTP/2 401
content-type: application/json; charset=utf-8
x-request-id: 428

{"statusCode":401,"message":"An error occurred","error":"unauthorized","requestId":90512}

That is the response for every authentication failure — bad signature, expired timestamp, replayed nonce, unknown key. Keep the requestId: it is the only handle that resolves to your request in our logs, and it is also returned as the X-Request-Id header.

Sign it

#!/usr/bin/env bash
# Usage: ONLINK_KEY_ID=pk_... ONLINK_SECRET=sk_... ./health.sh
set -euo pipefail

HOST="https://sandbox.onlink.africa"
METHOD="GET"
PATH_AND_QUERY="/v1/health"
BODY=""

TIMESTAMP="$(python3 -c 'import time; print(int(time.time() * 1000))')"
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"

# Five fields, newline-joined. printf, not echo -e: the trailing newline echo
# adds would be signed and the signature would not match.
SIGNING_STRING="$(printf '%s\n%s\n%s\n%s\n%s' \
  "$METHOD" "$PATH_AND_QUERY" "$TIMESTAMP" "$NONCE" "$BODY_HASH")"

SIGNATURE="$(printf '%s' "$SIGNING_STRING" \
  | openssl dgst -sha256 -hmac "$ONLINK_SECRET" -hex \
  | awk '{print $NF}')"

curl -i "${HOST}${PATH_AND_QUERY}" \
  -H "X-OnLink-Key: ${ONLINK_KEY_ID}" \
  -H "X-OnLink-Timestamp: ${TIMESTAMP}" \
  -H "X-OnLink-Nonce: ${NONCE}" \
  -H "X-OnLink-Signature: v1=${SIGNATURE}"

A successful call returns:

{ "status": "ok" }

status is always the exact string ok. If the service is not serving you get an HTTP error, not a degraded value in that field.

Then price something

POST /v1/quotes is the first call that does real work. Supply one leg and the other is derived:

{
  "side": "sell",
  "usdtAmount": "250.000000"
}

The response locks a rate:

{
  "quoteId": "f1c0a5d2-3b4e-4a71-9c8d-0e1f2a3b4c5d",
  "side": "sell",
  "kesAmount": "32025.00",
  "usdtAmount": "250.000000",
  "rate": "128.10",
  "expiresAt": "2026-09-04T09:31:30.000Z",
  "settlementEstimateSeconds": 1800
}

Three things to notice, because each is a decision you have to make now rather than later:

  • Every amount is a string. Parsing one into a binary float loses money quietly. See Money.
  • expiresAt is short and the quote is single-use. Create the order against it promptly; a spent or expired quote is a 409.
  • settlementEstimateSeconds is a banded estimate, not a promise. It tells you roughly what to tell your user, not what to time out on.

Amounts here are illustrative

The rate above is an example, not a live figure. Read rate, kesAmount and expiresAt off your own quote response — never assume a rate or a window.

Next

On this page