Skip to main content
This guide walks you through the steps to request, lock, and fulfill your first quote using CPN on EVM blockchains. There are two ways to integrate with CPN, using Transactions V1 or V2. This guide provides examples for both, and guidance when the workflow differs.
Note: This quickstart uses EVM blockchains to demonstrate how to create and execute a payment on CPN. For Solana, you would follow a similar process with some differences in the signing process.

Prerequisites

Note: This quickstart uses Circle Wallets to demonstrate how to create and execute a payment on CPN. To create and manage a Circle Wallet, you also need a Circle Developer account. You don’t need to use Circle Wallets (or a Circle Developer account) in your production implementation, however. You can bring your own wallet provider as long as it meets the requirements for CPN.
Before you begin this quickstart, ensure you have:
  • Obtained an API key for CPN from Circle
  • Created a Circle Developer Account
    • Registered your entity secret
    • Created a new wallet set using the API
    • Created at least one EOA wallet in the wallet set (see the link in the previous bullet)
      • If you are using Transactions V1, the wallet should be on EVM-TESTNET
      • If you are using Transactions V2, the wallet should be on ETH-SEPOLIA
    • Have access to the wallet ID for the EOA wallet
    • Funded the wallet with testnet USDC and testnet ETH.
  • Python installed on your development machine
    • The latest jwcrypto, web3, eth_utils, hexbytes, and eth_abi libraries are installed with the pip package manager
  • cURL installed on your development machine
  • (If using Transactions V2) Granted a USDC allowance to the Permit2 contract. See How-to: Grant USDC Allowance to Permit2 for more information.
  • (Optional) a configured webhook notification endpoint
This quickstart provides API requests in cURL format, along with example responses.
Note: The base URL for all API endpoints is https://api.circle.com/v1/cpn for both sandbox and production environments. The API determines if a request is for testnet or mainnet based on the key used to authenticate the request.

Part 1: Request a quote

Request quotes for a USDC to MX payment with the SPEI payment method. Request quotes with the create a quote endpoint, providing the source currency and destination amount. For Transactions V2, you must specify transactionVersion as VERSION_2. The endpoint returns a list of quotes from various BFIs with the rate, expiration time, USDC settlement window, and unique ID.
  • Transactions V2
  • Transactions V1
curl --request POST \
  --url https://api.circle.com/v1/cpn/quotes \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer ${YOUR_API_KEY}' \
  --header 'Content-Type: application/json' \
  --data '
{
    "paymentMethodType": "SPEI",
    "senderCountry": "US",
    "destinationCountry": "MX",
    "sourceAmount": {
        "currency": "USDC"
    },
    "destinationAmount": {
        "amount": "200",
        "currency": "MXN"
    },
    "blockchain": "ETH-SEPOLIA",
    "senderType": "INDIVIDUAL",
    "recipientType": "INDIVIDUAL",
    "transactionVersion": "VERSION_2"
}
'
Response
{
  "data": [
    {
      "id": "2792f4a6-f1bd-4435-b681-1da309122159",
      "paymentMethodType": "SPEI",
      "blockchain": "ETH-SEPOLIA",
      "senderCountry": "US",
      "destinationCountry": "MX",
      "createDate": "2025-09-24T00:01:13.532073875Z",
      "quoteExpireDate": "2025-09-24T00:01:42.502094Z",
      "cryptoFundsSettlementExpireDate": "2025-09-24T01:01:12.502097Z",
      "sourceAmount": {
        "amount": "15.000000",
        "currency": "USDC"
      },
      "destinationAmount": {
        "amount": "252.91",
        "currency": "MXN"
      },
      "fiatSettlementTime": {
        "min": "0",
        "max": "5",
        "unit": "MINUTES"
      },
      "exchangeRate": {
        "rate": "16.860667",
        "pair": "USDC/MXN"
      },
      "fees": {
        "totalAmount": {
          "amount": "1.568971",
          "currency": "USDC"
        },
        "breakdown": [
          {
            "type": "TAX_FEE",
            "amount": {
              "amount": "0.234663",
              "currency": "USDC"
            }
          },
          {
            "type": "BFI_TRANSACTION_FEE",
            "amount": {
              "amount": "0.138037",
              "currency": "USDC"
            }
          },
          {
            "type": "CIRCLE_SERVICE_FEE",
            "amount": {
              "amount": "0.000000",
              "currency": "USDC"
            }
          },
          {
            "type": "BLOCKCHAIN_GAS_FEE",
            "amount": {
              "amount": "1.196271",
              "currency": "USDC"
            }
          }
        ]
      },
      "senderType": "INDIVIDUAL",
      "recipientType": "INDIVIDUAL",
      "certificate": {
        // certificate object
      },
      "quoteOptions": {
        "isFirstParty": false
      },
      "transactionVersion": "VERSION_2"
    }
  ]
}

Part 2: Create a payment

Use the API to get the requirements for a payment, accept the quote, and create a payment.

2.1. Get payment requirements

Call the /payments/requirements endpoint with the quote ID to get the requirements for a payment. The endpoint returns an object describing the required fields for the compliance check. The optional field for each parameter defines if the parameter must be included in the response constructed in the next step.
curl -H "Authorization: Bearer ${YOUR_API_KEY}" \
  -X GET "https://api.circle.com/v1/cpn/payments/requirements?quoteId=${QUOTE_ID}"
Response
{
  "data": {
    "travelRule": [
      {
        "name": "ORIGINATOR_FINANCIAL_INSTITUTION_NAME",
        "type": "TEXT",
        "optional": false
      },
      {
        "name": "ORIGINATOR_FINANCIAL_INSTITUTION_ADDRESS",
        "type": "ADDRESS",
        "optional": false
      },
      {
        "name": "ORIGINATOR_FINANCIAL_INSTITUTION_ID",
        "type": "TEXT",
        "optional": true
      },
      {
        "name": "ORIGINATOR_NAME",
        "type": "TEXT",
        "optional": false
      },
      {
        "name": "ORIGINATOR_ACCOUNT_NUMBER",
        "type": "TEXT",
        "optional": false
      },
      {
        "name": "ORIGINATOR_ADDRESS",
        "type": "ADDRESS",
        "optional": false
      },
      {
        "name": "BENEFICIARY_NAME",
        "type": "TEXT",
        "optional": false
      },
      {
        "name": "BENEFICIARY_ADDRESS",
        "type": "ADDRESS",
        "optional": false
      },
      {
        "name": "ORIGINATOR_DATE_OF_BIRTH",
        "type": "TEXT",
        "optional": true
      },
      {
        "name": "ORIGINATOR_NATIONALITY",
        "type": "TEXT",
        "optional": true
      },
      {
        "name": "ORIGINATOR_NATIONAL_IDENTIFICATION_NUMBER",
        "type": "TEXT",
        "optional": true
      },
      {
        "name": "BENEFICIARY_DATE_OF_BIRTH",
        "type": "TEXT",
        "optional": true
      },
      {
        "name": "BENEFICIARY_NATIONALITY",
        "type": "TEXT",
        "optional": true
      },
      {
        "name": "BENEFICIARY_NATIONAL_IDENTIFICATION_NUMBER",
        "type": "TEXT",
        "optional": false
      },
      {
        "name": "BENEFICIARY_PHONE_NUMBER",
        "type": "TEXT",
        "optional": true
      },
      {
        "name": "BENEFICIARY_EMAIL",
        "type": "TEXT",
        "optional": true
      }
    ],
    "beneficiaryAccount": [
      {
        "name": "CLABE",
        "type": "TEXT",
        "optional": false
      }
    ]
  }
}

2.2. Encrypt the required fields

Construct a JSON object with the information requested in the previous step. For each schema, the properties that you must include are outlined by the optional field. Encrypt the object with the jwk certificate provided in the quote response. The correct format for travel rule data and beneficiary account data is a JSON array of objects where each object contains two properties: name and value. You can review an example of each field in the encryption how-to. Create a file called cpn_encryption.py and put the following code in it, replacing the requirements_response_json parameter with the contents of the response from the previous step, and the certificate_json parameter with the jwk from the quote response. When you run the script, it outputs the encrypted beneficiary and travel rule data to the console.
Python
"""
CPN Requirements V1 Encryption Quickstart

This script demonstrates how to:
1. Parse V1 Requirements response
2. Generate realistic test data matching the fields
3. Encrypt data using JWE for CPN API integration

Usage:
1. Replace certificate_json with your JWK from Quote response
2. Replace requirements_response_json with your Requirements response
3. Run the script to get encrypted data for creating payment API requests
"""

import json
import os
import base64
import random
from typing import Dict, Any, Optional, List
from jwcrypto import jwk, jwe

# ========================================
# Test Data Lists for Realistic Generation
# ========================================

FIRST_NAMES = [
    "James", "John", "Robert", "Michael", "William", "David", "Joseph", "Thomas",
    "Charles", "Mary", "Patricia", "Jennifer", "Linda", "Elizabeth", "Barbara",
    "Susan", "Jessica", "Sarah", "Karen", "Nancy"
]

LAST_NAMES = [
    "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis",
    "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez", "Wilson",
    "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin"
]

STREET_TYPES = ["St", "Ave", "Blvd", "Rd", "Ln", "Dr", "Way", "Circle", "Court"]
STREET_NAMES = [
    "Main", "Oak", "Maple", "Cedar", "Pine", "Elm", "Washington", "Lake", "Hill",
    "River", "Valley", "Park", "Spring", "Market", "Church", "Bridge", "Highland"
]

CITIES = [
    "New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia",
    "San Antonio", "San Diego", "Dallas", "San Jose", "Austin", "Jacksonville",
    "Fort Worth", "Columbus", "San Francisco", "Charlotte", "Indianapolis",
    "Seattle", "Denver", "Washington"
]

# ========================================
# Helper Functions
# ========================================

def generate_random_name() -> str:
    """Generate a random realistic name."""
    return f"{random.choice(FIRST_NAMES)} {random.choice(LAST_NAMES)}"

def generate_random_address() -> Dict[str, str]:
    """Generate a random realistic address."""
    street_number = str(random.randint(1, 9999))
    street_name = random.choice(STREET_NAMES)
    street_type = random.choice(STREET_TYPES)
    return {
        "street": f"{street_number} {street_name} {street_type}",
        "city": random.choice(CITIES),
        "country": "US",
        "postalCode": f"{random.randint(10000, 99999)}"
    }

def random_string(length: int = 12) -> str:
    """Generate a random string of given length."""
    return base64.b64encode(os.urandom(length)).decode()[:length]

def get_originator_name(case: Optional[str] = None) -> str:
    """Get the originator name based on test case."""
    if case == 'rfi-failed':
        return "Failed"
    return "Alice Johnson"  # Default for success case

# ========================================
# Core Data Generation
# ========================================

def generate_group_data(fields: List[Dict[str, Any]], originator_name: str) -> List[Dict[str, Any]]:
    """
    Generate test data matching Requirements fields as an array of {name, value}.

    Args:
        fields: List of field objects from Requirements
        originator_name: Name to use for originator fields

    Returns:
        List[{"name": str, "value": Any}] for required fields
    """
    items: List[Dict[str, Any]] = []

    for field in fields:
        name = field["name"]
        field_type = field["type"].upper()
        optional = field.get("optional", False)

        if optional:
            continue  # only include required fields

        # Address fields
        if field_type == "ADDRESS" or "ADDRESS" in name.upper():
            items.append({"name": name, "value": generate_random_address()})

        # Text fields
        elif field_type == "TEXT":
            if "NAME" in name.upper():
                if "ORIGINATOR" in name.upper():
                    items.append({"name": name, "value": originator_name})
                else:
                    items.append({"name": name, "value": generate_random_name()})
            elif "CLABE" in name.upper():
                items.append({"name": name, "value": ''.join(str(random.randint(0, 9)) for _ in range(18))})
            elif "ACCOUNT" in name.upper():
                items.append({"name": name, "value": ''.join(str(random.randint(0, 9)) for _ in range(12))})
            elif "DATE" in name.upper() or "BIRTH" in name.upper():
                year = random.randint(1970, 2000)
                month = random.randint(1, 12)
                day = random.randint(1, 28)
                items.append({"name": name, "value": f"{year:04d}-{month:02d}-{day:02d}"})
            elif "EMAIL" in name.upper():
                items.append({"name": name, "value": f"{random_string(8)}@example.com"})
            else:
                items.append({"name": name, "value": random_string(12)})

    return items

# ========================================
# Encryption
# ========================================

def encrypt_data(data: Any, jwk_data: Dict[str, Any]) -> str:
    """
    Encrypt data using JWE with provided JWK.

    Args:
        data: Data to encrypt (will be JSON serialized)
        jwk_data: JWK from certificate

    Returns:
        Encrypted JWE string
    """
    recipient_key = jwk.JWK(**jwk_data)
    jwe_obj = jwe.JWE(
        plaintext=json.dumps(data).encode(),
        protected=json.dumps({"alg": "ECDH-ES+A128KW", "enc": "A128GCM"})
    )
    jwe_obj.add_recipient(recipient_key)
    return jwe_obj.serialize(True)

# ========================================
# Configuration - Replace with your data
# ========================================

# Certificate JWK - copy from Quote response
# e.g. {"kty":"EC","crv":"P-256","kid":"263...5762","x":"Ydj...2Y","y":"n621...i8"}
certificate_json = '''certificate_json'''

# Requirements response - copy from Requirements API
# e.g. {"data": {"travelRule": [...], "beneficiaryAccount": [...]}}
requirements_response_json = '''requirements_response_json'''

# ========================================
# Main Execution
# ========================================

if __name__ == "__main__":
    # Parse configuration
    certificate = json.loads(certificate_json)
    required_fields = json.loads(requirements_response_json)

    # Extract field arrays
    travel_rule_fields = required_fields['data']['travelRule']
    beneficiary_account_fields = required_fields['data']['beneficiaryAccount']

    # Generate test data (array of {name, value})
    test_data = {
        "travelRuleData": generate_group_data(travel_rule_fields, get_originator_name()),
        "beneficiaryAccountData": generate_group_data(beneficiary_account_fields, get_originator_name())
    }

    # Create encrypted data (encrypt arrays directly)
    travel_rule_encrypted = encrypt_data(test_data["travelRuleData"], certificate)
    beneficiary_account_encrypted = encrypt_data(test_data["beneficiaryAccountData"], certificate)

    # Output encrypted data ready for API
    print(f"Travel Rule encryptedData: {travel_rule_encrypted}\n")
    print(f"Beneficiary Account encryptedData: {beneficiary_account_encrypted}")

2.3. Create a payment

After the quote is accepted, create a payment by calling the /payments endpoint. You need to provide the quote ID and encrypted sender and receiver information. The endpoint returns a unique payment ID and the initial status of the payment.
Note: You must create the payment before the quote expires, otherwise you’ll need to request a new quote.
Shell
curl --request POST \
  --url https://api.circle.com/v1/cpn/payments \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer ${YOUR_API_KEY}' \
  --header 'Content-type: application/json' \
  --data '
{
  "idempotencyKey" : "${randomUUID}",
  "quoteId" : "${cpn_ofi_quote_id}",
  "beneficiaryAccountData" : "${encrypted_beneficiary_data}",
  "travelRuleData" : "${encrypted_travel_rule_data}",
  "senderAddress" : "${YOUR_WALLET_ADDRESS}",
  "blockchain" : "ETH-SEPOLIA",
  "reasonForPayment" : "PMT001",
  "customerRefId" : "123c7442-e843-4afa-bfad-35f50636d35b",
  "refCode" : "7b479c5a-3684-4423-9fc6-f7c890c0e816",
  "useCase" : "B2B"
}
'
Response
JSON
{
  "data": {
    "id": "07dbe320-6bcb-475b-8d21-17b57263cd3e",
    "quoteId": "922a06cd-ff1e-4ee4-840e-54006893fd1a",
    "blockchain": "ETH-SEPOLIA",
    "paymentMethodType": "SPEI",
    "sourceAmount": {
      "amount": "10.000000",
      "currency": "USDC"
    },
    "destinationAmount": {
      "amount": "200.23",
      "currency": "MXN"
    },
    "status": "CRYPTO_FUNDS_PENDING",
    "refCode": "7b479c5a-3684-4423-9fc6-f7c890c0e816",
    "customerRefId": "123c7442-e843-4afa-bfad-35f50636d35b",
    "useCase": "B2B_INVOICE_PAYMENT",
    "expireDate": "2025-03-31T20:59:21.211547Z",
    "createDate": "2025-03-31T18:59:30.183044Z",
    "fees": {
      "totalAmount": {
        "amount": "0.170000",
        "currency": "USDC"
      },
      "breakdown": [
        {
          "type": "TAX_FEE",
          "amount": {
            "amount": "0.070000",
            "currency": "USDC"
          }
        },
        {
          "type": "BFI_TRANSACTION_FEE",
          "amount": {
            "amount": "0.100000",
            "currency": "USDC"
          }
        }
      ]
    },
    "fiatSettlementTime": {
      "min": "1",
      "max": "12",
      "unit": "HOURS"
    },
    "rfis": [],
    "onChainTransactions": []
  }
}

Part 3: Create a transaction

Use the API to create a blockchain transaction to transfer USDC. Sign the transaction locally, and use the API to broadcast it to the blockchain.
Note: This quickstart uses Circle Wallets to act as the originator wallet for the onchain payment. This section uses the wallet ID from the EOA wallet mentioned in the prerequisites section.

3.1. Initiate the onchain transaction

Initiate the onchain funds transfer by calling the /payments/{paymentId}/transactions endpoint with the payment ID from the previous step, and other transaction-related parameters. Note that if you are using Transactions V2, you should use the create transaction V2 endpoint. The endpoint returns an unsigned onchain transaction object and a transaction ID.
  • Transactions V2
  • Transactions V1
curl --request POST \
  --url https://api.circle.com/v2/cpn/payments/:paymentId/transactions \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer ${YOUR_API_KEY}' \
  --header 'Content-Type: application/json' \
  --data '
{
  "idempotencyKey" : "${RANDOM_UUID}"
}
'
Response
{
  "data": {
    "id": "dbc27d23-cd4f-447e-855e-349cb2853d23",
    "status": "CREATED",
    "paymentId": "49d4231e-6c4f-319e-946d-ed8c8bab5abc",
    "expireDate": "2025-09-08T20:02:06.651391Z",
    "blockchain": "ETH-SEPOLIA",
    "senderAddress": "0x57414adbBbc4BBA36f1dE26b2dc1648b28ae7799",
    "destinationAddress": "0xc75c3e371d617b3e60db1b6f3fa2f0689562e5a7",
    "amount": {
      "amount": "15.000000",
      "currency": "USDC"
    },
    "messageType": "PAYMENT_SETTLEMENT_CONTRACT_V1_0_PAYMENT_INTENT",
    "messageToBeSigned": {
      "domain": {
        "name": "Permit2",
        "chainId": "11155111",
        "verifyingContract": "0x000000000022D473030F116dDEE9F6B43aC78BA3"
      },
      "message": {
        "permitted": {
          "token": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
          "amount": "14174474"
        },
        "spender": "0xe2B17D0C1736dc7C462ABc4233C91BDb9F27DD1d",
        "nonce": "25668617285137697861288274946631174355105919960416755114569514179393151588120",
        "deadline": "1757362866",
        "witness": {
          "from": "0x57414adbBbc4BBA36f1dE26b2dc1648b28ae7799",
          "to": "0xc75c3e371d617b3e60db1b6f3fa2f0689562e5a7",
          "value": 14174474,
          "validAfter": "1757358106",
          "validBefore": "1757361726",
          "nonce": "0x38bfec2b230187932870d575132e8ae1f83b34c10e3bf6d64c377f0c13245718",
          "beneficiary": "0x4f1c3a0359A7fAd8Fa8E9E872F7C06dAd97C91Fd",
          "maxFee": "0",
          "attester": "0x768919ef04853b5fd444ccff48cea154768a0291",
          "requirePayeeSign": false
        }
      },
      "primaryType": "PermitWitnessTransferFrom",
      "types": {
        "EIP712Domain": [
          {
            "name": "name",
            "type": "string"
          },
          {
            "name": "chainId",
            "type": "uint256"
          },
          {
            "name": "verifyingContract",
            "type": "address"
          }
        ],
        "PermitWitnessTransferFrom": [
          {
            "name": "permitted",
            "type": "TokenPermissions"
          },
          {
            "name": "spender",
            "type": "address"
          },
          {
            "name": "nonce",
            "type": "uint256"
          },
          {
            "name": "deadline",
            "type": "uint256"
          },
          {
            "name": "witness",
            "type": "PaymentIntent"
          }
        ],
        "TokenPermissions": [
          {
            "name": "token",
            "type": "address"
          },
          {
            "name": "amount",
            "type": "uint256"
          }
        ],
        "PaymentIntent": [
          {
            "name": "from",
            "type": "address"
          },
          {
            "name": "to",
            "type": "address"
          },
          {
            "name": "value",
            "type": "uint256"
          },
          {
            "name": "validAfter",
            "type": "uint256"
          },
          {
            "name": "validBefore",
            "type": "uint256"
          },
          {
            "name": "nonce",
            "type": "bytes32"
          },
          {
            "name": "beneficiary",
            "type": "address"
          },
          {
            "name": "maxFee",
            "type": "uint256"
          },
          {
            "name": "requirePayeeSign",
            "type": "bool"
          },
          {
            "name": "attester",
            "type": "address"
          }
        ]
      }
    },
    "metadata": {}
  }
}

3.2 Sign the onchain transaction

Note: The following steps are for EVM blockchains. For Solana, you would follow a similar process with some differences in the signing. Please refer to How-to: Create an Onchain Transaction for more information.
  • Transactions V2
  • Transactions V1
Using the /sign/typedData endpoint, input the messageToBeSigned object from the previous step along with your entity secret and wallet ID. The transaction parameter should be stringified from the messageToBeSigned field from the transaction response.
Shell
curl --request POST \
  --url https://api.circle.com/v1/w3s/developer/sign/typedData \
  --header 'Accept: application/json' \
  --header 'authorization: Bearer ${YOUR_API_KEY}' \
  --header 'Content-Type: application/json' \
  --data '
{
  "entitySecretCiphertext": "qXnnGgbsU5lBUGiW9kp2/ltuvSSWW4qJ4/9VKuQT7wd6+ge2y7xqYnEc0pHbqLuj+YBDaPMfRUl1X+K1hbyiPTRVjCqHD5x3DyLtj8eTG/GmIimYfXOveXIJjsT95T8bI9uJ9kxygYAQbNev6wX993OYTYZ8D2PfVLUV3BicTSiClqhgSLW1Nh0qJ+TK0p2rOHs2HZkGA/WTv4SQv+uq//wEbUWFmrrD/ToTSuv3tMQvluCMYDF9xO/F6EoQwmP/XJCpPihGZuvrweTnhHbNWe5suvSSKpB+8Yo6f24ttNtCwvHrLBVaF6U9EZrCRpCydHJuuVBf5j7AD0JPC2DPFAG2p/Upq/KdzF1r8GJ4j2SsFLyzQEAw3ZAl623UiB/F3Szu2T/fYeF0rkfNt6tYKqmCmhvlzvn8BBkgIXsdcoEmNsf4x7b7UwPk9EloTibF4MhkGIW7jDHWWXlL3gKpGzMug+A2bIYdwUtqQ+u65pDi4+o+tuEH8MtM9Mmt3YaP2Zr40wj/uMnRv53hc+Apzsvh6UIsmliK2ldPyfXg77eDEzU7E228al/jIi2YQacQLNAAV870v3iKFB0PeWiUNtVlUdnqXmZkMA/bmg4TOo05ROGJWkfPVFWUNoocyEvCfEasj0ZflfbO8W2Q0M9BqhqjU/WHEBrYnF65ytY0A+8=",
  "data": "{\"domain\":{\"chainId\":\"11155111\",\"name\":\"USDC\",\"verifyingContract\":\"0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238\",\"version\":\"2\"},\"message\":{\"from\":\"0x39fd73b03a01c6230b5e0d946e1960d79db44fd8\",\"nonce\":\"0x854f1f66cb7cb0e266e17a3715c24c8dae1eb540c4eb00a7a1b39f4bfa9bcf09\",\"to\":\"0x6734b39043f1029f8d5f1b6948d5417b75a72cf8\",\"validAfter\":\"1743522751\",\"validBefore\":\"1743530842\",\"value\":\"10000000\"},\"primaryType\":\"TransferWithAuthorization\",\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"TransferWithAuthorization\":[{\"name\":\"from\",\"type\":\"address\"},{\"name\":\"to\",\"type\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\"},{\"name\":\"validAfter\",\"type\":\"uint256\"},{\"name\":\"validBefore\",\"type\":\"uint256\"},{\"name\":\"nonce\",\"type\":\"bytes32\"}]}}",
  "walletId": "${YOUR_CIRCLE_WALLET_ID}"
}
'
Response
JSON
{
  "signature": "0x905d70de3f1d9e86b982f6aee2755807fcd50a11cd9035bf47845c856be920fc3b7af8d06bf953bfdecdcea4cc9250aeaeb178b50116774d6bfab37bcc3757621c"
}

3.3. Submit the signed transaction

  • Transactions V2
  • Transactions V1
Use the /v2/cpn/payments/{paymentId}/transactions/{transactionId}/submit endpoint to submit the transaction to be broadcast to the blockchain. You should submit the EIP-712 typed data signature you obtained from the /sign/typedData endpoint in step 3.2. as signedTransaction.
Shell
curl --request POST \
  --url https://api.circle.com/v2/cpn/payments/:paymentId/transactions/:transactionId/submit \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer ${YOUR_API_KEY}' \
  --header 'Content-type: application/json' \
  --data '
{
  "signedTransaction": "0x12b5fb72e99f9bb0300d2eb66a6d89dd5a667f43669893cf14bfcc390754dcb61b69f92cba598ec83a184e11c97e3bb9964a2bfd7a09688eee63f586ad9ccae21c"
}
'
Response
JSON
{
  "data": {
    "id": "5cae9e1c-f3e3-44e5-ac36-d78f4ff9c56e",
    "status": "PENDING",
    "paymentId": "2b2b314a-0c06-39bb-b111-506f56599a17",
    "expireDate": "2025-11-12T00:08:42.000875Z",
    "blockchain": "ETH-SEPOLIA",
    "senderAddress": "0x57414adbBbc4BBA36f1dE26b2dc1648b28ae7799",
    "destinationAddress": "0xded12af48fb343b446bcbe739c5211636896362b",
    "amount": {
      "amount": "11.948672",
      "currency": "USDC"
    },
    "messageType": "PAYMENT_SETTLEMENT_CONTRACT_V1_0_PAYMENT_INTENT",
    "messageToBeSigned": {
      "domain": {
        "name": "Permit2",
        "chainId": "11155111",
        "verifyingContract": "0x000000000022D473030F116dDEE9F6B43aC78BA3"
      },
      "message": {
        "permitted": {
          "token": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
          "amount": "11948672"
        },
        "spender": "0x8ea7239f185CC32AB1Ff698f1b1A3aAB615D6d2c",
        "nonce": "55519981872451242578307489093459806523820915276389791540432104685160022674073",
        "deadline": "1762907262",
        "witness": {
          "from": "0x57414adbBbc4BBA36f1dE26b2dc1648b28ae7799",
          "to": "0xded12af48fb343b446bcbe739c5211636896362b",
          "value": 11048874,
          "validAfter": "1762901905",
          "validBefore": "1762906122",
          "nonce": "0x7abf323679377bff4d064e663e44b7064985eab99693dec4a9e8a9f941a80a99",
          "beneficiary": "0x8049E74C07A6BAdc8ddeB7C3530Ab9Af30037211",
          "maxFee": "899798",
          "attester": "0xcf9e077c75ce6bd22f48163e559d20b10708ae85",
          "requirePayeeSign": false
        }
      },
      "primaryType": "PermitWitnessTransferFrom",
      "types": {
        "EIP712Domain": [
          {
            "name": "name",
            "type": "string"
          },
          {
            "name": "chainId",
            "type": "uint256"
          },
          {
            "name": "verifyingContract",
            "type": "address"
          }
        ],
        "PermitWitnessTransferFrom": [
          {
            "name": "permitted",
            "type": "TokenPermissions"
          },
          {
            "name": "spender",
            "type": "address"
          },
          {
            "name": "nonce",
            "type": "uint256"
          },
          {
            "name": "deadline",
            "type": "uint256"
          },
          {
            "name": "witness",
            "type": "PaymentIntent"
          }
        ],
        "TokenPermissions": [
          {
            "name": "token",
            "type": "address"
          },
          {
            "name": "amount",
            "type": "uint256"
          }
        ],
        "PaymentIntent": [
          {
            "name": "from",
            "type": "address"
          },
          {
            "name": "to",
            "type": "address"
          },
          {
            "name": "value",
            "type": "uint256"
          },
          {
            "name": "validAfter",
            "type": "uint256"
          },
          {
            "name": "validBefore",
            "type": "uint256"
          },
          {
            "name": "nonce",
            "type": "bytes32"
          },
          {
            "name": "beneficiary",
            "type": "address"
          },
          {
            "name": "maxFee",
            "type": "uint256"
          },
          {
            "name": "requirePayeeSign",
            "type": "bool"
          },
          {
            "name": "attester",
            "type": "address"
          }
        ]
      }
    },
    "encodedMessageToBeSigned": "0xabc6f65eb8b2c264ae486b7244e9ca887cd0f8bd29422f651042665c14974ef3",
    "metadata": {},
    "version": "VERSION_2"
  }
}
Note: transactionHash will be provided after transaction is in COMPLETED status. You can monitor for the transactions webhook events to get the transactionHash.
Once the onchain transaction is confirmed by the BFI, the BFI initiates a fiat payout to the recipient. As the fiat payout progresses, the OFI is notified by webhook notifications.