📩 Webhooks

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:

HeaderDescription
eventThe type of event, e.g. payment, payout, movement
X-Infinia-SignatureHMAC-SHA256 signature of the payload — use this to verify authenticity
X-Idempotency-KeyA 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:

  1. Compute an HMAC-SHA256 of the raw request body using your secretId as the key
  2. Base64-encode the result
  3. Compare it to the X-Infinia-Signature header 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 2xx response 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.

AttemptTiming
1st retry15 minutes after initial failure
2nd retry15 minutes after 1st retry
3rd retry15 minutes after 2nd retry
4th retry15 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/32
  • 34.192.190.151/32

Sandbox

  • 44.214.68.30/32
  • 52.20.45.108/32
🔒

IP filtering alone is not sufficient — always verify the X-Infinia-Signature header. 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"
  }
}