Create a FIAT → FIAT Transfer

This guide shows the exact sequence to execute a FIAT→FIAT transfer using Penny API

FIAT→FIAT Transfer (Register Beneficiary → Quote → Deposit w/ Auto Payout)

This guide shows the exact sequence to execute a FIAT→FIAT transfer using Penny API:

  1. Register a 3rd-party beneficiary (creates fiatAccountId)
  2. Create an FX quote (creates quoteId)
  3. Create a deposit intent referencing quoteId + fiatAccountId so Penny can auto-execute the payout after funds arrive

Auth for all calls below uses api-key + api-secret headers.


Prerequisites

  • You have api-key and api-secret

  • You have a SendercustomerIdwith an approved KYC

  • You know:

    • Source currency / amount (e.g., MXN 100000)
    • Destination currency / amount (e.g., ARS)
    • Beneficiary banking details for destination rail (e.g., SPEI CLABE for MX, CVU/CBU for AR, etc.)

Base URL: https://penny-api-restricted-dev.alfredpay.io/api/v1/third-party-service/penny


1) Register Beneficiary (Third-Party Fiat Account)

Creates a beneficiary record and returns fiatAccountId. Critical: set "isExternal": true.

Endpoint

POST /fiatAccounts

Example (Mexico SPEI / CLABE)

curl --location 'https://penny-api-restricted-dev.alfredpay.io/api/v1/third-party-service/penny/fiatAccounts' \
  --header 'Content-Type: application/json' \
  --header 'accept: */*' \
  --header 'api-key: xxxxxxx' \
  --header 'api-secret: xxxxxxx' \
  --data '{
    "customerId": "21ef567b-d4c5-4bad-b4ce-ce975cc1ac57",
    "type": "SPEI",
    "fiatAccountFields": {
      "accountNumber": "012020477538404708",
      "accountType": "CLABE"
    },
    "isExternal": true
  }'

Response (example)

{
  "fiatAccountId": "797466c0-9774-4875-acfd-06adcc16596c",
  "type": "SPEI",
  "accountNumber": "012020477538404708",
  "accountType": "CLABE",
  "bankName": "BBVA MEXICO",
  "createdAt": "2024-12-03T15:48:54.557Z"
}

Save: fiatAccountId You will pass it in Step 3 under metadata.fiatAccountId.


2) Create Quote (FIAT→FIAT)

Creates the corridor pricing reference that will drive execution once funds arrive.

Endpoint

POST /quotes

Example (MXN → ARS, lock via fromAmount)

curl --request POST \
  --url 'https://penny-api-restricted-dev.alfredpay.io/api/v1/third-party-service/penny/quotes' \
  --header 'api-key: xxxxxxx' \
  --header 'api-secret: xxxxxxx' \
  --header 'content-type: application/json' \
  --data '{
    "fromCurrency": "MXN",
    "toCurrency": "ARS",
    "fromAmount": "100000"
  }'

Save: quoteId from the response. That quoteId becomes the single source of truth for:

  • corridor (fromCurrency, toCurrency)
  • rate/fees
  • expiry behavior

3) Create Deposit with Auto-Payout Metadata

This creates the “deposit order” that:

  • generates deposit instructions (where sender deposits funds)
  • links the deposit to quoteId
  • links the payout destination via metadata.fiatAccountId

Endpoint

POST /deposit

Example (Deposit Intent referencing quoteId + beneficiary fiatAccountId)

curl --request POST \
  --url 'https://penny-api-restricted-dev.alfredpay.io/api/v1/third-party-service/penny/deposit' \
  --header 'Content-Type: application/json' \
  --header 'accept: */*' \
  --header 'api-key: xxxxxxx' \
  --header 'api-secret: xxxxxxx' \
  --data '{
    "customerId": "48ab353c-b1f5-438c-affc-3ba0fb694e1c",
    "quoteId": "a38fe669-e754-47e8-a629-23c1902314c4",
    "fromCurrency": "MXN",
    "toCurrency": "USD",
    "amount": "300",
    "paymentMethodType": "BANK",
    "metadata": {
      "fiatAccountId": "b57e8612-0ab2-47d4-b353-ecaa6374c192"
    }
  }'

What you should expect back

Your deposit response should include:

  • a deposit/onramp identifier (e.g., TransactionId )
  • deposit instructions (account details + required reference/memo)
  • status indicating it is awaiting deposit

Save the TransactionID identifier + deposit reference/instructions. That reference is what the sender must include (memo/reference field) for auto-matching. The sender is expected to complete a push payment to the account provided in the deposit instructions.


What happens after Step 3 (Execution Lifecycle)

Once the sender deposits funds using the instructions/reference from Step 3:

  1. Penny detects & matches inbound funds to the deposit intent
  2. Penny emits FIAT_DEPOSIT_RECEIVED
  3. Penny executes the corridor defined by quoteId (FX/fees)
  4. Penny initiates payout to the beneficiary referenced by metadata.fiatAccountId
  5. Penny emits payout lifecycle events:
    • FIAT_TRANSFER_INITIATED
    • Then FIAT_TRANSFER_COMPLETED marking the beneficiary has been paid out.

Webhooks to listen for (Recommended)

To run this flow reliably, subscribe to webhooks for the lifecycle.

Minimum recommended events:

  • FIAT_DEPOSIT_RECEIVED (Deposit received)
  • TRADE_COMPLETED (FX Completed)
  • FIAT_TRANSFER_INITIATED (payout created)
  • FIAT_TRANSFER_COMPLETED (payout completed)

{
  "eventType": "FIAT_DEPOSIT_RECEIVED",
  "timestamp": "2026-01-15T19:02:11Z",
  "data": {
    "onrampId": "or_01HZX33B2A3G7Z0KXKQ9Y7D5V1",
    "sourceAmount": "50000.00",
    "sourceCurrency": "MXN",
    "senderName": "Empresa ABC SA de CV",
    "senderBankName": "BBVA México",
    "reference": "ALF-OR-01HZX33B2A3G7Z0KXKQ9Y7D5V1"
  }
}

Minimal “Do This” Checklist

  1. Create beneficiary

    • call POST /fiatAccounts with isExternal: true
    • store fiatAccountId
  2. Create quote

    • call POST /quotes
    • store quoteId and expiry
  3. Create deposit intent

    • call POST /deposit with:

      • quoteId
      • metadata.fiatAccountId
    • show sender the deposit instructions + required reference

  4. Listen to webhooks

    • confirm deposit received, payout initiated, payout settled