{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-products/fincore/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Idempotency","siteUrl":"https://docs.monato.com","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"idempotency","__idx":0},"children":["Idempotency"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Idempotency guarantees that multiple submissions of the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["same request"]}," result in ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["a single effect"]}," and ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["the same response"]},". It prevents duplicates and makes retries safe and predictable."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Scope"]},": applies to endpoints that support it. ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Today:"]}," Money Out",{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["TTL"]},": 24 hours from the first request"]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"enable-idempotency","__idx":1},"children":["Enable Idempotency"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Add the header below to any request you want to make idempotent."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Header"]},":",{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Idempotency-Key: <uuid-v5>"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Notes"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["If you don’t send the header, the request behaves as usual (no idempotency)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The first response (success ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["or"]}," error) is cached for 24h; identical retries return the same response from cache."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The same key ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["must"]}," be reused with the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["same body"]},". If the body changes, generate a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["new"]}," key."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"idempotency-behavior-quick-reference","__idx":2},"children":["Idempotency behavior (quick reference)"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Scenario"},"children":["Scenario"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Typical response"},"children":["Typical response"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Notes"},"children":["Notes"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["No ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Idempotency-Key"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["2xx / 4xx / 5xx"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Traditional flow (no idempotency)."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Invalid UUID format for idempotency-key"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["400 Bad Request"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Input validation error."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Idempotency key mismatch (payload or namespace)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["409 Conflict"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Payload or namespace does not match cached key."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Operation money_out in progress."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["409 Conflict"]}," (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["operation_in_progress"]},")"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Near-simultaneous duplicate."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Transaction amount format is invalid."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["400 Bad Request"]}," ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["(cached)"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["First error is cached for TTL."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Missing resource (e.g., instrument not found)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["500 Internal Server Error"]}," ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["(cached)"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["First error is cached for TTL."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Internally, idempotency keys are stored in an in-memory layer (Valkey/Redis-compatible) with a time-to-live (TTL) of 24 hours. The first result, whether a success (2xx) or an error (4xx/5xx), is associated with the key and reused for all identical retries during that period."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"money-out--using-idempotency","__idx":3},"children":["Money Out — Using Idempotency"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["With your default source Instrument and a valid destination Instrument, send Money Out requests with the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Idempotency-Key"]}," header to make retries safe."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Endpoint"]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /v1/transactions/money_out"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"successful-request-cached-for-24h","__idx":4},"children":["Successful request (cached for 24h)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Request"]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Path parameters"]},": none",{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Query Parameters"]},": none",{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Headers"]},":",{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Idempotency-Key: 66c0b04f-97d6-592d-8396-199819064afa"]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorization: Bearer <token>"]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Content-Type: application/json"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Request Body"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"client_id\": \"c2d1d1e3-3340-4170-980e-e9269bbbc551\",\n  \"source_instrument_id\": \"709448c3-7cbf-454d-a87e-feb23801269a\",\n  \"destination_instrument_id\": \"dd7f8d89-94dd-43ca-871b-720fde378b52\",\n  \"transaction_request\": {\n    \"external_reference\": \"7654329\",\n    \"description\": \"lorem ipsum dolor sit amet\",\n    \"amount\": \"1.95\",\n    \"currency\": \"MXN\"\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Response"]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Status Code"]},": 200 OK",{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Response Body"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"id\": \"16811ee8-1ef9-4dd4-8d84-9c2df89cf302\",\n  \"bankId\": \"9d84b03a-28d1-4898-a69c-38824239e2b1\",\n  \"clientId\": \"c2d1d1e3-3340-4170-980e-e9269bbbc551\",\n  \"externalReference\": \"7654329\",\n  \"trackingId\": \"20250306FINCHVLIKQ5SKUM\",\n  \"description\": \"lorem ipsum dolor sit amet\",\n  \"amount\": \"1.95\",\n  \"currency\": \"MXN\",\n  \"category\": \"DEBIT_TRANS\",\n  \"subCategory\": \"SPEI_DEBIT\",\n  \"transactionStatus\": \"INITIALIZED\",\n  \"audit\": {\n    \"createdAt\": \"2025-03-06 11:57:55.408000-06:00\",\n    \"updatedAt\": \"2025-03-06 11:57:55.408000-06:00\",\n    \"deletedAt\": \"None\",\n    \"blockedAt\": \"None\"\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Identical retry behavior"]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},"Send the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["same"]}," request again within 24h using the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["same"]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Idempotency-Key"]}," and ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["same"]}," body.",{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Status Code"]},": 200 OK (served from cache) — ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["same body"]}," as above."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"mismatch-example-same-key-different-body","__idx":5},"children":["Mismatch example (same key, different body)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you reuse the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["same"]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Idempotency-Key"]}," but change the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["body"]}," (e.g., a different ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amount"]},"), the request is rejected."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Request Body"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"client_id\": \"c2d1d1e3-3340-4170-980e-e9269bbbc551\",\n  \"source_instrument_id\": \"709448c3-7cbf-454d-a87e-feb23801269a\",\n  \"destination_instrument_id\": \"dd7f8d89-94dd-43ca-871b-720fde378b52\",\n  \"transaction_request\": {\n    \"external_reference\": \"7654329\",\n    \"description\": \"lorem ipsum dolor sit amet\",\n    \"amount\": \"2.10\",\n    \"currency\": \"MXN\"\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Response"]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Status Code"]},": 409 Conflict",{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Response Body"]}," (example):"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"code\": 6,\n  \"message\": \"API Error\",\n  \"details\": [\n    {\n      \"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\",\n      \"reason\": \"UNIQUE_VIOLATION\",\n      \"domain\": \"CORE\",\n      \"metadata\": {\n        \"error_detail\": \"Idempotency key does not match the request payload or the namespace may be incorrect\",\n        \"http_code\": \"409\",\n        \"module\": \"Transactions\",\n        \"method_name\": \"RegisterMoneyOut\",\n        \"error_code\": \"10-E4001\"\n      }\n    }\n  ]\n}\n\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"in-progress-example-near-simultaneous-duplicates","__idx":6},"children":["In-progress example (near-simultaneous duplicates)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If two identical requests arrive nearly at the same time, the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["first"]}," proceeds; the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["second"]}," returns “in progress”."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Response"]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Status Code"]},": 409 Conflict",{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Response Body"]}," (example):"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"code\": 6,\n  \"message\": \"API Error\",\n  \"details\": [\n    {\n      \"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\",\n      \"reason\": \"UNIQUE_VIOLATION\",\n      \"domain\": \"CORE\",\n      \"metadata\": {\n        \"error_detail\": \"Operation money_out in progress\",\n        \"http_code\": \"409\",\n        \"module\": \"Transactions\",\n        \"method_name\": \"RegisterMoneyOut\",\n        \"error_code\": \"10-E4001\"\n      }\n    }\n  ]\n}\n\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"deterministic-key-creation-uuid-v5","__idx":7},"children":["Deterministic Key Creation (UUID v5)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The idempotency key is ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["client-generated"]}," and ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["deterministic"]}," so the same ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["(client_id, method, canonical body)"]}," yields the same key."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Format"]},": UUID v5 (name-based)",{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Namespace"]},": UUID provided per environment (QA/Stg/Prod)",{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Method"]},": public alias, today: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"money_out\""]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Body Hash"]},": SHA-256 of the canonical JSON request body, with object keys sorted alphabetically at every level"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Formula"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"name = client_id + method + body_hash\nIdempotency-Key = UUIDv5(namespace, name)\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The request body must be canonicalized before hashing. All object keys, including nested objects, must be sorted alphabetically and serialized consistently so the same payload always produces the same hash."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"python--sample","__idx":8},"children":["Python — sample"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"import json\nimport hashlib\nimport uuid\n\n\"\"\"\nFixed namespace by environment used to generate deterministic UUIDv5 idempotency keys.\nUUIDv5 guarantees that the same inputs always generate the same output.\n\"\"\"\nNAMESPACE_IDEMPOTENCY = uuid.UUID(\"086fc9ec-d591-4045-bde4-3f9439506b08\")\n\n\ndef calculate_body_hash(data: dict) -> str:\n    \"\"\"\n    Generates a SHA-256 hash of the request body.\n\n    IMPORTANT:\n    The body keys are sorted alphabetically before serialization.\n    This ensures a deterministic JSON representation of the payload.\n\n    Without sorting, two semantically identical bodies with different\n    key orders would produce different hashes and therefore different\n    idempotency keys.\n\n    The separators argument removes unnecessary whitespace so the\n    serialized JSON remains consistent across environments.\n    \"\"\"\n    json_string = json.dumps(data, sort_keys=True, separators=(',', ':'))\n\n    # Generate SHA-256 hash of the normalized JSON string\n    return hashlib.sha256(json_string.encode(\"utf-8\")).hexdigest()\n\n\ndef generate_idempotency_key(client_id: str, method: str, body_hash: str) -> str:\n    \"\"\"\n    Generates the final idempotency key.\n\n    The key is derived from:\n    - client_id (caller identity)\n    - method (API operation)\n    - body_hash (deterministic representation of the request body)\n\n    Because UUIDv5 is deterministic, identical inputs will always produce\n    the same idempotency key, enabling safe request retries.\n    \"\"\"\n    return str(uuid.uuid5(NAMESPACE_IDEMPOTENCY, client_id + method + body_hash))\n\n\n# Usage Example:\nmethod = \"money_out\"\nrequest_body = {\n    \"client_id\": \"b000654b-4d12-46e5-b451-662459b6effc\",\n    \"source_instrument_id\": \"83fe58c6-15ad-4dd5-a4f2-ae7e5b39753a\",\n    \"destination_instrument_id\": \"206509fc-f879-4fa7-b6b1-243073fd94e3\",\n    \"transaction_request\": {\n        \"amount\": \"0.01\",\n        \"currency\": \"MXN\",\n        \"description\": \"FINCO PAY CTA MENSUAL SPEI\",\n        \"external_reference\": \"1236\"\n    }\n}\nclient_id = \"b000654b-4d12-46e5-b451-662459b6effc\"\n\n# Step 1: Generate deterministic hash of the normalized request body\nbody_hash = calculate_body_hash(request_body)\n\n# Step 2: Generate deterministic idempotency key\nidempotency_key = generate_idempotency_key(client_id, method, body_hash)\n\n# This value should be sent in the request header: Idempotency-Key\nprint(f\"Idempotency-Key: {idempotency_key}\")\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Expected output (with the sample above)"]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Idempotency-Key: 66c0b04f-97d6-592d-8396-199819064afa"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"nodejs--sample","__idx":9},"children":["Node.js — sample"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"const crypto = require(\"crypto\");\nconst { v5: uuidv5 } = require(\"uuid\");\n\n/*\n  Fixed namespace by environment used to generate deterministic UUIDv5 idempotency keys.\n  UUIDv5 guarantees that the same inputs always generate the same output.\n*/\nconst NAMESPACE_IDEMPOTENCY = \"086fc9ec-d591-4045-bde4-3f9439506b08\";\n\n/*\n  Recursively sorts all object keys alphabetically.\n  This ensures nested objects are normalized before hashing.\n*/\nfunction sortObjectRecursively(value) {\n  if (Array.isArray(value)) {\n    return value.map(sortObjectRecursively);\n  }\n\n  if (value !== null && typeof value === \"object\") {\n    return Object.keys(value)\n      .sort()\n      .reduce((result, key) => {\n        result[key] = sortObjectRecursively(value[key]);\n        return result;\n      }, {});\n  }\n\n  return value;\n}\n\n/*\n  Generates a SHA-256 hash of the request body.\n\n  IMPORTANT:\n  The body keys are sorted alphabetically before serialization.\n  This ensures a deterministic JSON representation of the payload.\n\n  Without sorting, two semantically identical bodies with different\n  key orders would produce different hashes and therefore different\n  idempotency keys.\n*/\nfunction calculateBodyHash(data) {\n  const normalizedBody = sortObjectRecursively(data);\n  const jsonString = JSON.stringify(normalizedBody);\n\n  // Generate SHA-256 hash of the normalized JSON string\n  return crypto.createHash(\"sha256\").update(jsonString).digest(\"hex\");\n}\n\n/*\n  Generates the final idempotency key.\n\n  The key is derived from:\n  - clientId (caller identity)\n  - method (API operation)\n  - bodyHash (deterministic representation of the request body)\n\n  Because UUIDv5 is deterministic, identical inputs will always produce\n  the same idempotency key, enabling safe request retries.\n*/\nfunction generateIdempotencyKey(clientId, method, bodyHash) {\n  return uuidv5(clientId + method + bodyHash, NAMESPACE_IDEMPOTENCY);\n}\n\n// API method\nconst method = \"money_out\";\n\n/*\n  Example request body.\n  Note: The original key order does not matter because calculateBodyHash()\n  will normalize it before hashing.\n*/\nconst requestBody = {\n  client_id: \"b000654b-4d12-46e5-b451-662459b6effc\",\n  source_instrument_id: \"83fe58c6-15ad-4dd5-a4f2-ae7e5b39753a\",\n  destination_instrument_id: \"206509fc-f879-4fa7-b6b1-243073fd94e3\",\n  transaction_request: {\n    amount: \"0.01\",\n    currency: \"MXN\",\n    description: \"FINCO PAY CTA MENSUAL SPEI\",\n    external_reference: \"1236\",\n  },\n};\n\nconst clientId = \"b000654b-4d12-46e5-b451-662459b6effc\";\n\n// Step 1: Generate deterministic hash of the normalized request body\nconst bodyHash = calculateBodyHash(requestBody);\n\n// Step 2: Generate deterministic idempotency key\nconst idempotencyKey = generateIdempotencyKey(clientId, method, bodyHash);\n\n// This value should be sent in the request header: Idempotency-Key\nconsole.log(`Idempotency-Key: ${idempotencyKey}`);\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Expected output (with the sample above)"]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Idempotency-Key: 66c0b04f-97d6-592d-8396-199819064afa"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"important-notes","__idx":10},"children":["Important notes"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Namespace per environment"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each environment (QA, Staging, Production) has its own ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["NAMESPACE_IDEMPOTENCY"]},"."," ","This prevents collisions between keys generated across environments."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Canonical JSON serialization"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The request body must be canonicalized before hashing. All object keys, including nested objects, must be sorted alphabetically and serialized consistently."," ","This ensures the hash is identical even if the original JSON key order varies."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Client–Server consistency"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["body_hash"]}," calculation must be identical to what FINCO’s backend uses."," ","Make sure you use the same serialization logic and hashing algorithm."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Guaranteed uniqueness"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If any of the three components change (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["client_id"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["method"]},", or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["body"]},"), the resulting UUID will also change."]}]}]}]},"headings":[{"value":"Idempotency","id":"idempotency","depth":1},{"value":"Enable Idempotency","id":"enable-idempotency","depth":2},{"value":"Idempotency behavior (quick reference)","id":"idempotency-behavior-quick-reference","depth":3},{"value":"Money Out — Using Idempotency","id":"money-out--using-idempotency","depth":2},{"value":"Successful request (cached for 24h)","id":"successful-request-cached-for-24h","depth":3},{"value":"Mismatch example (same key, different body)","id":"mismatch-example-same-key-different-body","depth":3},{"value":"In-progress example (near-simultaneous duplicates)","id":"in-progress-example-near-simultaneous-duplicates","depth":3},{"value":"Deterministic Key Creation (UUID v5)","id":"deterministic-key-creation-uuid-v5","depth":2},{"value":"Python — sample","id":"python--sample","depth":3},{"value":"Node.js — sample","id":"nodejs--sample","depth":3},{"value":"Important notes","id":"important-notes","depth":3}],"frontmatter":{"seo":{"title":"Idempotency"}},"lastModified":"2026-03-11T23:01:54.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/products/fincore/guides/idempotency","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}