celerlinksdevelopers
Celerlinks / Integration guides

Webhooks & signatures

Receive signed transaction updates without polling. Configure separate endpoints for sandbox and live traffic.

Add your endpoint

  1. Open Developers → Webhooks and choose the environment.
  2. Enter a public HTTPS URL on port 443 and select your transaction events.
  3. Copy the one-time signing secret to your server. It is different from an API key.
  4. Send a test from the portal and inspect its delivery history.
Configure webhooks

We do not follow redirects or deliver to private networks. One endpoint is supported per account per environment. Events are captured after configuration; existing transactions are not backfilled.

Event types

transaction.processing, transaction.successful, and transaction.failed report transaction lifecycle changes for the account. webhook.test is sent only when you request a connection test. Event and ticket changes do not emit webhooks yet.

Example event envelope
{
  "id": "EVENT_UUID",
  "type": "transaction.successful",
  "environment": "sandbox",
  "created_at": "2026-09-06T12:00:00+00:00",
  "data": {
    "id": 123,
    "reference": "sb_EXAMPLE",
    "status": "successful",
    "amount": 1000,
    "currency": "NGN",
    "payment_status": "paid"
  }
}

Verify before processing

The X-Celerlinks-Signature header contains t=TIMESTAMP,v1=HEX_SIGNATURE. Compute HMAC-SHA256 over the timestamp, a period, and the exact raw request body. Compare signatures in constant time and reject timestamps outside a five-minute window. Parse JSON only after verification.

Node.js signature verification (server-side)
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyWebhook(rawBody, signatureHeader, secret) {
  if (!secret || !Buffer.isBuffer(rawBody)) return false;
  const match = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(signatureHeader || "");
  if (!match) return false;
  const [, timestamp, supplied] = match;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = createHmac("sha256", secret)
    .update(timestamp + ".").update(rawBody).digest();
  return timingSafeEqual(expected, Buffer.from(supplied, "hex"));
}
// Preserve the raw bytes in your HTTP framework before its JSON parser.
// After verification, persist the event with a UNIQUE constraint on event.id.
// Return 2xx only once the event has been stored durably for processing.

Delivery and retries

Deliveries are processed every minute. A 2xx response marks delivery successful. Timeouts, connection failures, redirects and other HTTP responses retry after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, for at most six attempts per cycle. Requests have a 10-second timeout.

The event ID and payload remain unchanged across retries; each attempt gets a fresh timestamp and signature. Delivery is at least once and may arrive out of order. Deduplicate using the signed body’s id (also sent as X-Celerlinks-Event-ID), acknowledge duplicates with 2xx, and fetch current transaction details if order matters.

Manage delivery

The portal shows payloads, response codes, attempts, and the next retry. Failed deliveries can be retried manually. Pausing an endpoint holds pending deliveries; enabling resumes them. Removing an endpoint deletes its history. Changing its URL cancels pending events for the old address.

Rotate secrets

Rotation invalidates the previous secret immediately. New attempts, including retries, use the replacement. Pause the endpoint, rotate and save the new secret on your server, then enable it. Keep the old verifier briefly for requests already in flight.

Payment confirmation

Check the event’s transaction status. A successful payment initialization is not confirmation that a product has been delivered. Fetch the transaction for full details such as electricity tokens; webhook payloads contain only a compact status summary.