Verifying Webhook Verification the OneKhusa Way
Learn how to securely verify OneKhusa webhooks locally with HMAC-SHA512, eliminating extra API calls while protecting your integration against forged and tampered requests.

No extra API round trip. No shared-secret guesswork. Just a hash you compute yourself.
Your webhook endpoint is the one part of your integration that lives on the public internet with the lights on. Anyone who discovers the URL can POST to it. If your handler credits a wallet or marks an order as paid based on nothing more than “a request arrived,” you have a payment system that anyone with curl can operate.
Signature verification is what closes that gap and as of the 19 August 2026 release, OneKhusa lets you do it entirely inside your own application.
What changed
Previously, verifying a webhook meant calling OneKhusa back: you sent the received event code and signature to the Verify Webhook API and waited for a verdict. Every single webhook cost you an outbound HTTPS request before you could safely act on it.
Now you verify locally, using your webhook secret and an HMAC-SHA512 hash of the payload you just received.
| Old flow | New flow | |
|---|---|---|
| Verification | Round trip to OneKhusa’s Verify Webhook API | Computed in-process |
| Latency per webhook | An additional API call overhead to processing your transaction | instant hash |
| Failure mode | Network hiccup blocks verification | None there is no external dependency |
| What you need | API credentials, event code, signature | Webhook secret, raw body, signature |
The practical win isn’t just speed. It’s that verification no longer has a dependency that can go down, time out, or rate-limit you during a traffic spike exactly the moment you’re least able to absorb a backlog of unverifiable events.
Why HMAC-SHA512?
This isn’t a OneKhusa invention. HMAC-based request signing is the standard pattern across the payments industry Stripe, Paystack, Flutterwave and Other major payment providers all sign webhooks the same way, differing mainly in hash function and header name.
The mechanism is worth understanding rather than copy-pasting:
HMAC (Hash-based Message Authentication Code) combines a secret key with a message to produce a fixed-length tag. Only someone holding the secret can produce a valid tag for a given message, and the tag changes completely if a single byte of the message changes. That gives you two properties at once:
- Authenticity — the notification really came from OneKhusa, because only OneKhusa and you hold the webhook secret.
- Integrity — the payload wasn’t altered in transit, because any modification invalidates the tag.
SHA-512 is the underlying hash. HMAC is deliberately not the same thing as sha512(secret + payload) that naive construction is vulnerable to length-extension attacks. HMAC’s nested-hash design is what makes it safe, which is why you should always reach for your language’s hmac primitive rather than hand-rolling one.
One thing HMAC does not give you is freshness. The signature covers the payload, not the time. A valid webhook captured and replayed later still carries a valid signature.
Five steps, in this exact order:
- Extract the headers.
X-OneKhusa-Webhook-Signaturecarries the HMAC-SHA512 digest.OneKhusa-Webhook-Eventcarries the event code telling you what happened. - Read the raw body. The exact bytes as received unparsed, unmodified.
- Compute the HMAC. HMAC-SHA512 over the raw body, keyed with your webhook secret, hex-encoded.
- Compare in constant time. Use a fixed-time equality function, never
==. - Only then, act. Parse the JSON and finalise the transaction in your system.
The ordering matters. Parse after you verify, not before.
Three ways this goes wrong
Nearly every failed verification traces back to one of these.
1. You verified a re-serialised payload, not the raw one.
This is the big one. Your framework parses the JSON body into an object; you then re-serialise it to feed the hash. But JSON.stringify doesn’t reproduce the original byte-for-byte whitespace collapses, key order can shift, Unicode escapes and number formatting change. The hash is computed over bytes, so a semantically identical payload produces a completely different digest.
Capture the raw body before any middleware touches it, and hash that.
2. You compared with ==.
String equality short-circuits on the first differing character, so a wrong guess that shares a longer prefix takes measurably longer to reject. An attacker who can send many requests and time the responses can walk the correct signature out of you one character at a time. Use timingSafeEqual, hash_equals, or FixedTimeEquals the whole point is that they take the same time regardless of where the difference lies.
Watch the length check: Node’s crypto.timingSafeEqual throws if the buffers differ in length, so compare lengths first and return false.
3. Case and encoding mismatches.
The digest is lowercase hex. Normalise the incoming header with .toLowerCase() before comparing, and make sure both sides are UTF-8. If your secret is stored with a trailing newline from a copy-paste or a .env quoting quirk, every signature will fail check that first when a working integration suddenly breaks.
Beyond the signature
A valid signature tells you the event is authentic. It doesn’t tell you it’s new, and it doesn’t finish the job.
Handle events idempotently. Because HMAC doesn’t cover time, a replayed webhook still verifies. Retries mean you’ll legitimately receive the same event more than once anyway. Key your processing on the transaction reference and make a repeat delivery a no-op — never a second credit.
Acknowledge fast, process later. Return 2xx as soon as verification passes, then hand the work to a queue. Long synchronous processing inside the handler invites timeouts, which invite retries, which compound the load. OneKhusa retries failed deliveries with exponential backoff behind a circuit breaker — a handler that’s slow rather than broken can still trip it.
Treat the secret like a private key. Environment variables or a secrets manager, never source control, never client-side code, never a log line. Rotate it if you suspect exposure, and if you need zero-downtime rotation, verify against both the old and new secret during the overlap window.
Terminate TLS properly. Signature verification and HTTPS solve different problems. HMAC stops forgery and tampering; TLS stops eavesdropping and gives you server authentication. You want both.
The rule that matters
Receive → read raw body → verify signature → parse JSON → process event
Never trust a webhook simply because it reached your webhook URL. Reaching your URL proves nothing at all; the signature is the only thing that does.
Full reference, including the complete code samples: Verify Webhook — OneKhusa Docs
Garry Balala
Software Developer Advocate, OneKhusa