Skip to main content

Webhooks

If you give us an endpoint during onboarding, we notify it when an inbound operation reaches a terminal state.

Note

v1 settles synchronously, so the credit response already tells you the outcome. Webhooks are the durable backstop for a connection that dropped before you read that response — and they are how you will learn about SmartPay-initiated reversals.

Events

EventWhen
credit.completedA credit settled
credit.failedA credit was rejected or failed
credit.reversedA settled credit was reversed

Headers

HeaderPurpose
X-Olive-EventThe event name
X-Olive-Event-IdUnique delivery id. Use it to deduplicate
X-Olive-TimestampRFC3339 UTC, part of the signed material
X-Olive-SignatureHex HMAC-SHA256

Verifying

Sign <timestamp>.<raw body> with the webhook secret issued at onboarding, and compare in constant time.

Node.js
const crypto = require("crypto");

function verify(secret, timestamp, rawBody, signature) {
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");

return crypto.timingSafeEqual(
Buffer.from(signature, "utf8"),
Buffer.from(expected, "utf8"),
);
}
Python
import hashlib
import hmac

def verify(secret: str, timestamp: str, raw_body: bytes, signature: str) -> bool:
expected = hmac.new(
secret.encode(),
timestamp.encode() + b"." + raw_body,
hashlib.sha256,
).hexdigest()

return hmac.compare_digest(expected, signature)
Go
func Verify(secret, timestamp string, rawBody []byte, signature string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(timestamp))
mac.Write([]byte("."))
mac.Write(rawBody)

return hmac.Equal([]byte(signature), []byte(hex.EncodeToString(mac.Sum(nil))))
}
Warning

Verify against the raw request body, before any JSON parsing. A parsed and re-serialised body has different bytes and will not verify.

Payload

{
"event": "credit.completed",
"reference": "UBA-20260819-0001",
"inbound_id": "INB_7KP2M9XR4TQW",
"status": "COMPLETED",
"operation": "credit",
"amount": 50000,
"fee": 0,
"currency": "SLE",
"subscriber_id": "SUB_3XQ9M2KP",
"transaction_id": "TXN_4M8XQ2VN7PLK",
"occurred_at": "2026-08-19T14:22:01Z"
}

Delivery

Return any 2xx to acknowledge. Anything else is retried at 0s, 30s, 2m, 10m, 1h and 6h — six attempts, then the delivery is abandoned.

Verify, then acknowledge quickly

Return 200 as soon as you have verified and stored the event. Do your processing afterwards: we time out an attempt at 15 seconds.

Deduplicate on X-Olive-Event-Id

A retry can arrive after you already processed a delivery whose acknowledgement we never saw.

Treat webhooks as a hint, not the source of truth

The authority is always GET /api/v1/inbound/transactions/{reference}.