Webhooks let your application receive real-time notifications when something happens — a payment completes, a payout fails, a movement is posted — without polling the API.
When an event occurs, Infinia sends a POST request to the callback URL you configured, with a JSON payload describing the event.
Request headers
Every webhook request includes the following headers:
| Header | Description |
|---|---|
event | The type of event, e.g. payment, payout, movement |
X-Infinia-Signature | HMAC-SHA256 signature of the payload — use this to verify authenticity |
X-Idempotency-Key | A unique UUID for this delivery attempt — use this to prevent double-processing |
Verifying the signature
Always verify the X-Infinia-Signature header before processing a webhook. This confirms the request came from Infinia and the payload hasn't been tampered with.
How it works:
- Compute an HMAC-SHA256 of the raw request body using your
secretIdas the key - Base64-encode the result
- Compare it to the
X-Infinia-Signatureheader using a time-safe comparison
Always use a time-safe comparison function. Regular string equality (
==) is vulnerable to timing attacks.
import hmac
import base64
import hashlib
def is_valid_signature(raw_body: str, signature_header: str, client_id: str) -> bool:
computed = hmac.new(
key=client_id.encode('utf-8'),
msg=raw_body.encode('utf-8'),
digestmod=hashlib.sha256
).digest()
provided = base64.b64decode(signature_header)
return hmac.compare_digest(computed, provided)const crypto = require('crypto');
function isValidSignature(rawBody, signatureHeader, clientId) {
const computed = crypto
.createHmac('sha256', clientId)
.update(rawBody, 'utf8')
.digest();
let provided;
try {
provided = Buffer.from(signatureHeader, 'base64');
} catch {
return false;
}
if (computed.length !== provided.length) return false;
return crypto.timingSafeEqual(computed, provided);
}import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class WebhookVerifier {
public static boolean isValidSignature(String rawBody, String signatureHeader, String clientId) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(clientId.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] computed = mac.doFinal(rawBody.getBytes(StandardCharsets.UTF_8));
byte[] provided;
try {
provided = Base64.getDecoder().decode(signatureHeader);
} catch (IllegalArgumentException e) {
return false;
}
if (computed.length != provided.length) return false;
int result = 0;
for (int i = 0; i < computed.length; i++) result |= computed[i] ^ provided[i];
return result == 0;
}
}<?php
function isValidSignature(string $rawBody, string $signatureHeader, string $clientId): bool {
$computed = base64_encode(hash_hmac('sha256', $rawBody, $clientId, true));
return hash_equals($computed, $signatureHeader);
}
?>Preventing duplicate processing
Each delivery attempt includes a unique X-Idempotency-Key. If Infinia retries a failed delivery, the same key is reused, letting you detect and skip duplicates.
Recommended approach:
- Store processed idempotency keys (e.g. in a database or cache) for at least 24 hours
- Before processing a webhook, check if its key has already been handled
- If it has, return a
2xxresponse immediately without re-processing
Retry policy
If your server does not respond with a 2xx status code, or is unreachable, Infinia will retry delivery automatically.
| Attempt | Timing |
|---|---|
| 1st retry | 15 minutes after initial failure |
| 2nd retry | 15 minutes after 1st retry |
| 3rd retry | 15 minutes after 2nd retry |
| 4th retry | 15 minutes after 3rd retry |
After 4 failed retries, the webhook is marked as ERROR and no further automatic retries are made. You can manually resend any webhook in ERROR state via the API or dashboard.
Source IP addresses
Infinia sends webhooks exclusively from the following IP addresses. You can use these to configure firewall rules or allowlists.
Production
54.152.206.172/3234.192.190.151/32
Sandbox
44.214.68.30/3252.20.45.108/32
IP filtering alone is not sufficient — always verify the
X-Infinia-Signatureheader. IP addresses should be treated as an additional layer, not a replacement for signature verification.
Example payload
POST /your-webhook-endpoint HTTP/1.1
Host: example.com
Content-Type: application/json
event: movement
X-Infinia-Signature: 5f2f77a1c3f12e7c9f81b2e6f2d4d9b8e0d9a1a4a2b4d8a6f0f1a9b8e0d3c1f0
X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
{
"id": "338131",
"account_id": "640",
"company_id": "12344",
"amount": 10,
"currency": "MXN",
"country": "MX",
"balance": 10,
"description": "Initial deposit",
"created_at": "2025-08-11T16:05:06.749462",
"updated_at": "2025-08-11T16:05:06.749462",
"third_party": null,
"operation": null,
"company": {
"id": "12344",
"name": "InfiniaCompany"
}
}
