API reference

Webhook signature verification

Verify every webhook before you trust it. Each delivery carries a Credicorp-Signature header: a timestamp and an HMAC-SHA256 of timestamp.rawbody, keyed by your signing secret. Recompute it, compare in constant time, and reject anything older than the 5-minute tolerance. Never parse the body until the signature checks out.

2 min read

HMAC-SHA256Signature algorithm
t=…,v1=…Header format
300 sTimestamp tolerance

The signature header

Each request includes:

Credicorp-Signature: t=1751619922,v1=5f8d1c…

t is the Unix timestamp the signature was generated. v1 is the hex HMAC-SHA256 of the string {t}.{raw_request_body}, keyed by your endpoint’s whsec_ signing secret. Multiple v1= values may appear during a secret rotation; accept the request if any one matches.

Verify in Node

const crypto = require('crypto');
function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
  const signed = `${parts.t}.${rawBody}`;
  const expected = crypto.createHmac('sha256', secret).update(signed).digest('hex');
  const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
  const fresh = Math.abs(Date.now()/1000 - Number(parts.t)) < 300;
  return ok && fresh;
}

Verify in PHP

function verify(string $raw, string $header, string $secret): bool {
    parse_str(str_replace(',', '&', $header), $p);
    $expected = hash_hmac('sha256', $p['t'] . '.' . $raw, $secret);
    $fresh = abs(time() - (int)$p['t']) < 300;
    return $fresh && hash_equals($expected, $p['v1']);
}

Rules that matter

  • Use the raw bytes. Compute the HMAC over the exact body you received, before any JSON parse or framework re-encoding.
  • Compare in constant time (timingSafeEqual / hash_equals) to avoid timing side channels.
  • Enforce the 300-second tolerance to blunt replay. Combined with idempotency on the event id, this stops a captured request being replayed later.

Frequently asked questions

Why must I use the raw body, not the parsed JSON?

HMAC is over exact bytes. If your framework re-serialises the JSON (reordering keys, changing whitespace), the recomputed signature will not match. Capture the raw body before any parsing middleware runs.

What if the timestamp is outside the tolerance?

Reject the request — return a 400. A stale timestamp usually means a replay or a badly delayed retry. Legitimate retries are re-signed with a fresh timestamp, so they always fall inside the window.

Do I still need idempotency if I verify signatures?

Yes. Signature verification proves authenticity and freshness; it does not stop an authentic event being delivered twice. Deduplicate on the event id — see idempotency.

Funding for UK limited companies

Credicorp lends to your company, not to you personally — short-term working capital with no personal guarantee. See what your business could access.