Skip to content
Last updated

Idempotency guarantees that multiple submissions of the same request result in a single effect and the same response. It prevents duplicates and makes retries safe and predictable.

Scope: applies to endpoints that support it. Today: Money Out
TTL: 24 hours from the first request


Enable Idempotency

Add the header below to any request you want to make idempotent.

Header:
Idempotency-Key: <uuid-v5>

Notes

  • If you don’t send the header, the request behaves as usual (no idempotency).
  • The first response (success or error) is cached for 24h; identical retries return the same response from cache.
  • The same key must be reused with the same body. If the body changes, generate a new key.

Idempotency behavior (quick reference)

ScenarioTypical responseNotes
No Idempotency-Key2xx / 4xx / 5xxTraditional flow (no idempotency).
Invalid UUID format for idempotency-key400 Bad RequestInput validation error.
Idempotency key payload mismatch.409 ConflictPayload does not match cached key.
Operation money_out in progress.409 Conflict (operation_in_progress)Near-simultaneous duplicate.
Transaction amount format is invalid.400 Bad Request (cached)First error is cached for TTL.
Missing resource (e.g., instrument not found)500 Internal Server Error (cached)First error is cached for TTL.

Money Out — Using Idempotency

With your default source Instrument and a valid destination Instrument, send Money Out requests with the Idempotency-Key header to make retries safe.

Endpoint
POST /v1/transactions/money_out

Successful request (cached for 24h)

Request
Path parameters: none
Query Parameters: none
Headers:
Idempotency-Key: 66c0b04f-97d6-592d-8396-199819064afa
Authorization: Bearer <token>
Content-Type: application/json

Request Body:

{
  "client_id": "c2d1d1e3-3340-4170-980e-e9269bbbc551",
  "source_instrument_id": "709448c3-7cbf-454d-a87e-feb23801269a",
  "destination_instrument_id": "dd7f8d89-94dd-43ca-871b-720fde378b52",
  "transaction_request": {
    "external_reference": "7654329",
    "description": "lorem ipsum dolor sit amet",
    "amount": "1.95",
    "currency": "MXN"
  }
}

Response
Status Code: 200 OK
Response Body:

{
  "id": "16811ee8-1ef9-4dd4-8d84-9c2df89cf302",
  "bankId": "9d84b03a-28d1-4898-a69c-38824239e2b1",
  "clientId": "c2d1d1e3-3340-4170-980e-e9269bbbc551",
  "externalReference": "7654329",
  "trackingId": "20250306FINCHVLIKQ5SKUM",
  "description": "lorem ipsum dolor sit amet",
  "amount": "1.95",
  "currency": "MXN",
  "category": "DEBIT_TRANS",
  "subCategory": "SPEI_DEBIT",
  "transactionStatus": "INITIALIZED",
  "audit": {
    "createdAt": "2025-03-06 11:57:55.408000-06:00",
    "updatedAt": "2025-03-06 11:57:55.408000-06:00",
    "deletedAt": "None",
    "blockedAt": "None"
  }
}

Identical retry behavior
Send the same request again within 24h using the same Idempotency-Key and same body.
Status Code: 200 OK (served from cache) — same body as above.


Mismatch example (same key, different body)

If you reuse the same Idempotency-Key but change the body (e.g., a different amount), the request is rejected.

Request Body:

{
  "client_id": "c2d1d1e3-3340-4170-980e-e9269bbbc551",
  "source_instrument_id": "709448c3-7cbf-454d-a87e-feb23801269a",
  "destination_instrument_id": "dd7f8d89-94dd-43ca-871b-720fde378b52",
  "transaction_request": {
    "external_reference": "7654329",
    "description": "lorem ipsum dolor sit amet",
    "amount": "2.10",
    "currency": "MXN"
  }
}

Response
Status Code: 409 Conflict
Response Body (example):

{
    "code": 6,
    "message": "API Error",
    "details": [
        {
            "@type": "type.googleapis.com/google.rpc.ErrorInfo",
            "reason": "UNIQUE_VIOLATION",
            "domain": "CORE",
            "metadata": {
                "error_detail": "Idempotency key payload mismatch",
                "http_code": "409",
                "module": "Transactions",
                "method_name": "RegisterMoneyOut",
                "error_code": "10-E4001"
            }
        }
    ]
}

In-progress example (near-simultaneous duplicates)

If two identical requests arrive nearly at the same time, the first proceeds; the second returns “in progress”.

Response
Status Code: 409 Conflict
Response Body (example):

{
    "code": 6,
    "message": "API Error",
    "details": [
        {
            "@type": "type.googleapis.com/google.rpc.ErrorInfo",
            "reason": "UNIQUE_VIOLATION",
            "domain": "CORE",
            "metadata": {
                "error_detail": "Operation money_out in progress",
                "http_code": "409",
                "module": "Transactions",
                "method_name": "RegisterMoneyOut",
                "error_code": "10-E4001"
            }
        }
    ]
}

Deterministic Key Creation (UUID v5)

The idempotency key is client-generated and deterministic so the same (client_id, method, body) yields the same key.

Format: UUID v5 (name-based)
Namespace: UUID provided per environment (QA/Stg/Prod)
Method: public alias, today: "money_out"
Body Hash: SHA-256 of the request JSON with keys sorted (canonicalized)

Formula

name = client_id + method + body_hash
Idempotency-Key = UUIDv5(namespace, name)

Python — sample

import json
import hashlib
import uuid

# Namespace UUID assigned by FINCO (varies by environment)
NAMESPACE_IDEMPOTENCY = uuid.UUID("086fc9ec-d591-4045-bde4-3f9439506b08")


def calculate_body_hash(data: dict) -> str:
    """
    Calculates the SHA-256 hash of the body, ensuring the order of the keys.
    """
    json_string = json.dumps(data, sort_keys=True, separators=(',', ':'))
    return hashlib.sha256(json_string.encode("utf-8")).hexdigest()


def generate_idempotency_key(client_id: str, method: str, body_hash: str) -> str:
    """
    Generates the deterministic Idempotency-Key in UUID v5 format.
    """
    return str(uuid.uuid5(NAMESPACE_IDEMPOTENCY, client_id + method + body_hash))


# Usage Example:
method = "money_out"
request_body = {
    "client_id": "b000654b-4d12-46e5-b451-662459b6effc",
    "source_instrument_id": "83fe58c6-15ad-4dd5-a4f2-ae7e5b39753a",
    "destination_instrument_id": "206509fc-f879-4fa7-b6b1-243073fd94e3",
    "transaction_request": {
        "amount": "0.01",
        "currency": "MXN",
        "description": "FINCO PAY CTA MENSUAL SPEI",
        "external_reference": "1236"
    }
}
client_id = "b000654b-4d12-46e5-b451-662459b6effc"

body_hash = calculate_body_hash(request_body)
idempotency_key = generate_idempotency_key(client_id, method, body_hash)

print(f"Idempotency-Key: {idempotency_key}")

Expected output (with the sample above)
Idempotency-Key: 66c0b04f-97d6-592d-8396-199819064afa

Node.js — sample

const crypto = require("crypto");
const { v5: uuidv5 } = require("uuid");

const NAMESPACE_IDEMPOTENCY = "086fc9ec-d591-4045-bde4-3f9439506b08";

function calculateBodyHash(data) {
  const jsonString = JSON.stringify(
    Object.keys(data)
      .sort()
      .reduce((obj, key) => ((obj[key] = data[key]), obj), {})
  );
  return crypto.createHash("sha256").update(jsonString).digest("hex");
}

function generateIdempotencyKey(clientId, method, bodyHash) {
  return uuidv5(clientId + method + bodyHash, NAMESPACE_IDEMPOTENCY);
}

const method = "money_out";
const requestBody = {
  client_id: "b000654b-4d12-46e5-b451-662459b6effc",
  source_instrument_id: "83fe58c6-15ad-4dd5-a4f2-ae7e5b39753a",
  destination_instrument_id: "206509fc-f879-4fa7-b6b1-243073fd94e3",
  transaction_request: {
    amount: "0.01",
    currency: "MXN",
    description: "FINCO PAY CTA MENSUAL SPEI",
    external_reference: "1236",
  },
};
const clientId = "b000654b-4d12-46e5-b451-662459b6effc"

const bodyHash = calculateBodyHash(requestBody);
const idempotencyKey = generateIdempotencyKey(clientId, method, bodyHash);

console.log(`Idempotency-Key: ${idempotencyKey}`);

Expected output (with the sample above)
Idempotency-Key: 66c0b04f-97d6-592d-8396-199819064afa

Important notes

  • Namespace per environment

    Each environment (QA, Staging, Production) has its own NAMESPACE_IDEMPOTENCY. This prevents collisions between keys generated across environments.

  • JSON key order

    The request body must be serialized with keys sorted alphabetically (sort_keys=True). This ensures the hash is identical even if the original JSON key order varies.

  • Client–Server consistency

    The body_hash calculation must be identical to what FINCO’s backend uses. Make sure you use the same serialization logic and hashing algorithm.

  • Guaranteed uniqueness

    If any of the three components change (client_id, method, or body), the resulting UUID will also change.