Webhooks
If you give us an endpoint during onboarding, we notify it when an inbound operation reaches a terminal state.
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
| Event | When |
|---|---|
credit.completed | A credit settled |
credit.failed | A credit was rejected or failed |
credit.reversed | A settled credit was reversed |
Headers
| Header | Purpose |
|---|---|
X-Olive-Event | The event name |
X-Olive-Event-Id | Unique delivery id. Use it to deduplicate |
X-Olive-Timestamp | RFC3339 UTC, part of the signed material |
X-Olive-Signature | Hex HMAC-SHA256 |
Verifying
Sign <timestamp>.<raw body> with the webhook secret issued at onboarding, and
compare in constant time.
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"),
);
}
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)
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))))
}
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.
Return 200 as soon as you have verified and stored the event. Do your
processing afterwards: we time out an attempt at 15 seconds.
A retry can arrive after you already processed a delivery whose acknowledgement we never saw.
The authority is always
GET /api/v1/inbound/transactions/{reference}.