Documentation index for AI agents (llms.txt)

Append .md to any page URL for its markdown source, or fetch llms-full.txt for the complete corpus.

Webhooks

Security

Verify webhook signatures and handle retries

Every webhook from iwoca includes a cryptographic signature so you can verify it's genuine before processing. Signatures use a Hash-based Message Authentication Code (HMAC), which proves both that the payload is unchanged and that it came from someone holding your secret token.

Signature verification

Each webhook includes two headers:

HeaderPurpose
X-IW-SignatureHMAC-SHA256 signature of the request
X-IW-TimestampUnix timestamp when the webhook was sent

The signature is computed as:

HMAC-SHA256(secret_token, timestamp + "." + request_body)

The result is base64-encoded and prefixed with sha256=, so the header looks like sha256=YoSHTwbVZnuLY8kPusTeHoqcyQ9g1IeVHKpIFLJjp4k=.

A small number of older integrations are still on SHA-1, where the signature is sha1= + base64 of HMAC-SHA1(secret_token, request_body) – over the body alone, with no timestamp. GET /webhooks/configuration/ returns an encryption_method of sha256 or sha1 so you can check which applies to you. Everything below assumes sha256; if you are on sha1, talk to your Partnership Manager about moving across.

Code examples

import base64
import hmac
import hashlib
import time
def verify_webhook(request_body: bytes, signature: str, timestamp: str, secret: str) -> bool:
# Reject old webhooks (replay protection)
if abs(time.time() - int(timestamp)) > 300:
return False
digest = hmac.new(
secret.encode(),
f"{timestamp}.".encode() + request_body,
hashlib.sha256
).digest()
# The header is base64-encoded and prefixed with "sha256=" —
# not a hex digest.
expected = "sha256=" + base64.b64encode(digest).decode()
return hmac.compare_digest(expected, signature)

Replay protection

Use X-IW-Timestamp to reject stale webhooks. We recommend rejecting any webhook where the timestamp is more than five minutes old. This prevents replay attacks where a captured webhook is resent later.

Retry behaviour

A delivery counts as successful when your endpoint responds with a status below 400. Anything else – a 4xx, a 5xx, a connection error, or a timeout – is a failed attempt and is retried.

By default iwoca retries up to 10 times after the first attempt, so 11 attempts in all, spread over roughly 16 hours:

AttemptSent after the previous attempt
1– (immediately after the event)
210 seconds
31 minute
42 minutes
55 minutes
615 minutes
730 minutes
81 hour
92 hours
104 hours
118 hours

A few seconds of random jitter is added to each gap, so treat the times as approximate. Each attempt waits up to 10 seconds for your response before being treated as a timeout.

These are the default timings. The schedule is configurable per partner. A custom one changes both the intervals and the number of attempts, so confirm yours with your Partnership Manager if your integration depends on either.

What each retry looks like

  • X-IW-Event-ID stays the same across every attempt of the same event – use it as your idempotency key.
  • X-IW-Timestamp and X-IW-Signature are regenerated on every attempt, because the timestamp is part of the signed payload.

Because each attempt carries a fresh timestamp, your signature verification must use the timestamp from the request in hand – never a cached value from an earlier attempt.

Point your subscription at the final URL, not a redirect. Redirects are followed, but a 301, 302 or 303 is followed as a bodyless GET. Your handler still receives the X-IW-* headers, but the event payload is gone. The redirect target's 2xx makes the delivery look successful to iwoca, so it is never retried. 307 and 308 do replay the full POST. If the redirect crosses to another host, the signature headers are forwarded to that host as well. Register the exact HTTPS URL you want to receive events on.

After the last attempt

If all attempts fail, iwoca raises an internal alert and the delivery stops. Your subscription stays active – it is not disabled automatically, and events after that point are still delivered normally. Nothing is re-sent to you automatically. If your endpoint has been down, reconcile by calling the relevant GET endpoints — your Partnership Manager can arrange a manual redelivery if you need one.

A manual redelivery arrives as a separate event with its own X-IW-Event-ID, so it will not match the ID of the original attempt. If you need to detect that specific case, compare the payload fields as well as the event ID.

Test webhooks sent from the Developer Portal are attempted once and never retried, so you will not see retry behaviour while debugging there.

Idempotency

The same event may be delivered more than once. Your webhook handler should be idempotent – processing the same event twice should not cause issues. Use the X-IW-Event-ID header (a unique ID per webhook) as your idempotency key; fall back to the combination of event_type, timestamp, and the data fields if needed.

IP allowlisting

If you need IP-based filtering, contact your Partnership Manager for iwoca's webhook IP addresses.