Skip to main content

Authentication

Kanall has two authentication contexts: API key (server-to-server, for your backend) and session (browser-based, for the dashboard).


API key authentication

All /v1/* endpoints require an API key in the X-API-Key header:

X-API-Key: ten_sk_4a3b2c1d...

Rules:

  • Store your API key in environment variables only — never in source code, client-side JavaScript, or version control
  • If your key is compromised, rotate it via POST /auth/rotate-key (dashboard session required)
  • All API key requests are rate-limited per key. Requests that exceed the limit receive 429 Too Many Requests.

Obtaining an API key

Registration triggers an OTP email. Verify the OTP to receive your API key.

Step 1 — Register:

curl -X POST https://kanall.onrender.com/register \
-H "Content-Type: application/json" \
-d '{
"name": "Your Organisation",
"email": "ops@yourcompany.ng",
"password": "secure-password-min-8-chars"
}'
{
"tenantId": "550e8400-e29b-41d4-a716-446655440000"
}

Step 2 — Verify email:

curl -X POST https://kanall.onrender.com/auth/verify-email \
-H "Content-Type: application/json" \
-d '{
"tenantId": "550e8400-e29b-41d4-a716-446655440000",
"otp": "847291"
}'
{
"apiKey": "ten_sk_4a3b2c1d...",
"warning": "Store this API key securely — it will not be shown again."
}

Kanall stores only a SHA-256 hash of the key. The raw value is never retrievable after this response.


Session authentication (dashboard)

The Kanall web dashboard authenticates with email and password. On login, the server sets an httpOnly session cookie (kanall_session) with a 7-day expiry. The browser sends this cookie automatically — you never handle it in JavaScript.

Dashboard sessions and API keys are completely independent. An API key does not grant dashboard access.

Login

curl -X POST https://kanall.onrender.com/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "ops@yourcompany.ng", "password": "secure-password"}'

Logout

curl -X POST https://kanall.onrender.com/auth/logout

Session endpoints

These endpoints require a valid dashboard session cookie (kanall_session). They are not accessible with an API key.

Get current tenant

GET /auth/me

Returns the authenticated tenant's profile.

curl https://kanall.onrender.com/auth/me
{
"id": "550e8400-...",
"name": "StarLine Gas",
"email": "ops@starlinegas.ng",
"status": "active",
"apiKeySuffix": "...3c1d",
"kycStatus": "verified",
"businessType": "registered_business",
"createdAt": "2026-06-30T08:00:00Z"
}
FieldDescription
apiKeySuffixLast 4 characters of your API key — for visual confirmation only
kycStatusBusiness verification status: unverified, pending_review, or verified
businessTypeYour registered business type, or null if KYC not yet submitted

Rotate API key

POST /auth/rotate-key

Generates a new API key and immediately invalidates the previous one. Returns the new raw key — store it immediately.

curl -X POST https://kanall.onrender.com/auth/rotate-key
{
"apiKey": "ten_sk_9z8y7x6w..."
}

Submit business KYC

POST /auth/business-kyc

Submits your organisation's business verification details. This moves your account from unverified to verified. Required before provisioning accounts in production.

Request body:

FieldTypeRequiredDescription
businessTypestringYesOne of: sole_proprietor, registered_business, ngo, other
cacNumberstringNoCAC registration number (e.g. RC-1234567)
curl -X POST https://kanall.onrender.com/auth/business-kyc \
-H "Content-Type: application/json" \
-d '{
"businessType": "registered_business",
"cacNumber": "RC-1234567"
}'
{
"kycStatus": "verified",
"businessType": "registered_business"
}

See KYC concepts for the full two-layer KYC model (business + customer tiers).


Configure tenant webhook URL

POST /auth/webhook-url

Sets a single webhook URL that receives payment events for all your virtual accounts. This is the standard setup — configure it once and every account you provision will deliver to this endpoint automatically.

Request body:

FieldTypeRequiredDescription
urlstringYesYour backend webhook endpoint URL
curl -X POST https://kanall.onrender.com/auth/webhook-url \
-H "X-API-Key: ten_sk_..." \
-H "Content-Type: application/json" \
-d '{"url": "https://app.starlinegas.ng/webhooks/kanall"}'

Response: 200 OK

{
"webhookUrl": "https://app.starlinegas.ng/webhooks/kanall"
}

Once set, you do not need to pass callbackUrl when provisioning accounts. All deliveries go to this URL. The current configured URL is returned in GET /auth/me under webhookUrl.

:::tip Per-account override If a specific virtual account has its own callbackUrl set, that takes precedence over the tenant-level URL for that account only. Use this for testing individual accounts against a local tunnel (ngrok) without affecting your production endpoint. :::


Manage webhook signing secret

POST /auth/webhook-secret

Returns your outbound webhook signing secret. If you have not generated one yet, this creates and stores a new secret. Calling it again reveals the existing secret.

curl -X POST https://kanall.onrender.com/auth/webhook-secret
{
"webhookSecret": "a3f8b2c9d1e4f7a0b3c6d9e2f5a8b1c4d7e0f3a6b9c2d5e8f1a4b7c0d3e6f9a2"
}

The secret is stored encrypted (AES-256-GCM) server-side. The raw value is only visible through this endpoint.

Use this secret to verify outbound deliveries from Kanall — see Outbound webhook signing.


List misdirected payments

GET /auth/misdirected

Returns webhook events that passed signature verification but whose aliasAccountReference did not match any virtual account in the system. These are payments that arrived on Nomba's rails but cannot be attributed to a provisioned account.

This endpoint is session-only (dashboard access) — it is not available via API key. Misdirected events are system-wide; they have no tenant owner by definition.

curl https://kanall.onrender.com/auth/misdirected
{
"events": [
{
"ID": "e1f2a3b4-...",
"NombaTxnRef": "nom_txn_xyz789",
"SignatureValid": true,
"Status": "processed",
"Category": "misdirected",
"ErrorMessage": "account ref 'unknown-ref-999' not found",
"RetryCount": 0,
"ReceivedAt": "2026-07-02T14:22:00Z",
"ProcessedAt": "2026-07-02T14:22:01Z"
}
]
}

events is null if there are no misdirected events.


Unauthenticated endpoints

EndpointPurpose
POST /registerCreate a new tenant
POST /auth/loginDashboard login
POST /auth/verify-emailVerify email OTP and receive API key
GET /healthHealth check
POST /webhooks/nombaNomba inbound webhook (HMAC-authenticated, not API key)

Error responses

Missing or invalid API key:

HTTP/1.1 401 Unauthorized

{ "error": "unauthorized" }

Suspended tenant:

HTTP/1.1 403 Forbidden

{ "error": "tenant is suspended" }