Build a Modern Payments Infrastructure with Circle and USDC

USDC API Circle Mint Stablecoins Developer


Learn how to construct a robust payment infrastructure using Circle’s tools and the stability of USDC

Cross-border payments are notoriously painful. They are complex, time-consuming, and expensive. Transaction fees can be over 5%, funds can be held for little to no reason, and transfers are unacceptably slow. For many years, this has just been “the way it is.” But sticking with “the way it is” is costly, and with the advent of cryptocurrencies, keeping the status quo could be fatal to your business.

In this article we’ll look at the cryptocurrencies called stablecoins: what they are, why they are needed, and how they work. Finally, we’ll walk through a quick tutorial on how you can integrate stablecoins into your modern payments infrastructure using the US dollar-backed USDC.

Let’s start with an overview of stablecoins.


What Are Stablecoins?

Stablecoins are cryptocurrencies, deployed on a blockchain, designed to keep a stable value (hence the name stablecoins).

Many cryptocurrencies (just like stocks) are highly volatile. This makes them unsuitable for payments. For the same reason you don’t send shares in Apple to settle an invoice, you don’t typically send a cryptocurrency such as ETH (the token behind the blockchain Ethereum). ETH’s value in US dollars (USD) can swing by double-digit percentages in a single day.

Stablecoins were created to solve this problem by keeping a low rate of day-to-day volatility. While they still fluctuate in value, the changes are minimal, akin to a modern-day fiat currency such as the USD.

But stablecoins are still cryptocurrencies. This means you still get all the benefits of blockchain—they can be sent cross-border easily, in a matter of minutes (often seconds), and for negligible costs. And the payments are provable and fraud-proof.

Stablecoins give you the best of both worlds: the stability of a global fiat currency with easy, fast, and cheap transfers.


What Is USDC?

One of the most popular stablecoins in the world is USDC by Circle, a US-based, global financial technology company.

As you can probably guess from the name, USDC is a stable cryptocurrency whose value is pegged to the US dollar. This means that 1 USDC is, and will always be, equal to and redeemable for 1 USD. You can think of USDC as a digital dollar, available 24-7, moving at the speed of the internet, built for the next evolution in payments. It allows you to send and receive money in a near-instant, at near-zero cost, to more people in more places.

You can read more about how USDC keeps its value here. USDC is a highly-trusted asset with reserves held in the custody of leading financial institutions.. And Circle is a regulated money transmitter under US law, just like PayPal, Stripe, and Apple Pay.

If you want to future-proof your payments infrastructure, adding the ability to accept USDC to your current stack is an easy decision.

Let’s see just how easy it is with a tutorial on how to set up a payments infrastructure that utilizes Circle and USDC.


Tutorial: Integrating USDC Payments Using the Circle Accounts API

So let’s simulate the payment process using USDC to see how easy it is.

We’ll play the roles of both parties: the merchant who wants to accept a payment and a customer who wants to make the payment. First let’s do a little quick setup. We need a crypto wallet, some test funds, access to the Circle API, and Node/NPM installed.


Step 1: Install a crypto wallet

To deal with cryptocurrencies and stablecoins, you first need a crypto wallet to hold the coins. You can use almost any crypto wallet for this tutorial, but the most popular wallet, and one that is free and easy to use, is MetaMask. You can download and install it as a browser extension.

When you set up your wallet as a first-time user, MetaMask will lead you through a series of steps that will culminate in the generation of a secret wallet recovery phrase. Store this phrase carefully—you’ll need it later.

Once your wallet is created, you should be able to open your wallet extension to a window that looks something like this:




Every wallet has a unique address that identifies this account on the blockchain (on Ethereum, it starts with 0x). You can see your address on MetaMask.

ETH is the native currency of Ethereum—and note that we have none! Our wallet is empty. We’ll change that in just a moment.

First, we need to change networks. For this tutorial, we don’t want to use any real funds, so we’ll switch to Goerli, which is an Ethereum test network. By using Goerli, we can demonstrate moving funds around without spending any real money.

From the top left, select the network drop-down and toggle to “show test networks.” Next, select the Goerli testnet as our network of choice.


Step 2: Acquire funds

We need two kinds of funds to perform our simulation: goerliETH to pay gas fees and USDC to make the payment to the merchant.

Gas fees are the fees you pay to use a blockchain. Any time you create a transaction, you’ll need to send a small amount of that blockchain’s native currency to pay for that transaction. For Ethereum, that currency is ETH. Since we’re on the Goerli test network, we’ll pay with goerliETH. goerliETH is easy and free to get by using a faucet. (We can also use a faucet to get some free test USDC.)

To obtain the goerliETH, you can use the Alchemy faucet. Note that you may be required to create a free Alchemy account to use the faucet.




For obtaining test USDC, you can use the faucet from Circle.

Unlike goerliETH which shows up automatically in MetaMask, you’ll probably need to import the USDC token manually into your wallet. You can do so by clicking Refresh List on MetaMask.

An alternative way to acquire USDC is to swap some of your goerliETH on a platform such as Uniswap. You can acquire a large amount of test USDC this way on account of the test conversion rates.



Once you have both goerliETH and USDC, your wallet should look something like this:




Step 3: Create a Circle sandbox account

Now we’ll need access to the Circle API. Create a Circle sandbox account here.



Once you’ve logged in, you should see a dashboard that looks something like this:


Screenshot 2023-09-28 at 11.39.51 AM


From this page, you can get a sandbox API key. This API key will only be shown once, so make sure you save it safely. Don’t worry if you lose it, though. You can generate a new key.


Step 4: Install Node and NPM

Finally, if you don’t already have them on your machine, install the latest versions of Node and NPM.


Make a Payment with USDC

Our setup is complete! We’re ready to look at the technical details of making a USDC payment and enabling our tech stack to accept payments with USDC. Here are the steps involved.

  1. The customer agrees to pay the merchant a certain amount of USDC on a certain blockchain (in our case, Goerli). Note that Circle is supported on quite a few blockchains. We haven’t covered this yet, but will in a later article. For now, we are dealing exclusively with Ethereum and Ethereum test networks.
  2. The merchant programmatically creates a payment intent through Circle. Circle generates a wallet address to which the payment needs to be made. The merchant shares the address with the customer.
  3. The customer uses their crypto wallet to make a payment to the designated address.
  4. The merchant accesses payment status through the Circle dashboard and confirms payment.

Step 5: Create a payment intent

Let’s say the merchant has agreed to sell something to the customer for 1 USDC, and now would like to request payment.

As a merchant, we need to create a payment intent that will request payment from the customer. There is some code involved here, but note that most of it is needed as a one-time setup and can then be used repeatedly, and execution of the code would typically be performed programmatically from your infrastructure.

First, create an empty Node project and install all required dependencies, including the Circle SDK.

$ node -v && npm -v
$ mkdir payment-infra && cd payment-infra $ npm init -y $ npm install @circle-fin/circle-sdk ethers $ touch payments.js

Open the payments.js file and add the following code:

// Import necessary packages
const uuid = require("uuid")
const { Circle, CircleEnvironments, PaymentIntentCreationRequest } = require("@circle-fin/circle-sdk");

// Instantiate Circle SDK object with API key
const CIRCLE_API_KEY = 'SAND_API_KEY:a772d01fa660452b4fbf2d62b15692d0:ee43119fbf2e4c0677921f22f5ae66da'
const circle = new Circle(

// Create intent to pay 1 USDC
async function createUsdcPayment(amount) {
    const reqBody = {
        // Change amount here to whatever you wish
        amount: {
            amount: amount,
            currency: "USD"
        settlementCurrency: "USD",
        // Since we're using sandbox, ETH here refers to the goerli chain
        paymentMethods: [
                type: "blockchain",
                chain: "ETH"
        idempotencyKey: uuid.v4()

    // Create payment intent using SDK
    const resp = await circle.cryptoPaymentIntents.createPaymentIntent(reqBody);

// Get Payment intent
async function getPaymentIntent(paymentIntentId) {
    const resp = await circle.cryptoPaymentIntents.getPaymentIntent(paymentIntentId);

// Helper function to create a delay (in milliseconds)
function delay(time) {
    return new Promise((resolve) => setTimeout(resolve, time));

// Poll payment ID to get payment intent every 500 ms
async function pollPaymentIntent(paymentIntentId) {

    // Poll every 0.5 seconds or 500 ms
    const pollInterval = 500;

    let resp = undefined;
    while (true) {
        resp = await getPaymentIntent(paymentIntentId);

        // Check if deposit address has been created
        let depositAddress =[0].address;

        // If address created, break. Else check again in 500 ms
        if (depositAddress) break;
        await delay(pollInterval);


async function main() {

    // Create payment intent 
    const paymentIntent = await createUsdcPayment(“1”);
    const paymentIntentId =;
    const payment = await pollPaymentIntent(paymentIntentId);
    const address = payment.paymentMethods[0].address;

    // Get wallet address 
    console.log(`Payment Intend ID: ${paymentIntentId}`)
    console.log(`Please pay 1 USDC to ${address}`)


There is a lot of code here, but the core logic is extremely simple:

  1. We have a function `createUSDCPayment` that creates a payment intent using Circle for a certain amount of USDC.
  2. We have a helper function that gets the latest status of the payment intent we create (by intent ID).
  3. Finally, we have a poll intent function that polls the helper function from (2) until a wallet address is generated.

Run this code using the following:

$ node payments.js

You should see output that looks something like this:

Payment Intend ID: 76075284-212d-4268-9dfe-4780a6744beb
Please pay 1 USDC to 0x0d53d534bb7bb10b7bfe50095a113a579759900a

This wallet address for payment would next be shared with the customer (typically through email/UI/etc) along with instructions to send 1 USDC.


Step 6: Make the payment

Let’s now put on the customer hat and make the 1 USDC payment through MetaMask.

In your wallet extension, click on the USDC token from the Assets tab. This will open a new tab with the option to send USDC.




Use the address from the previous step as the intended receiver and 1 USDC as the amount to be paid.

Notice that in this screen, you will see that you’re actually making two payments: the 1 USDC payment that goes to the merchant and the goerliETH payment that goes to the network.

Also notice how incredibly cheap it is to make this transaction! Even in mainnet (production) networks, the transaction fees are often a few cents and are flat fees, independent of the amount being transferred. You can send 1 USDC or 1,000,000 USDC for the same transaction cost.

And note how fast the payment is made. Typically the transaction takes just a few seconds to execute and confirm.


Step 7: Receive payment confirmation

The final step is for the merchant to receive confirmation that payment has been made.

There are two ways to do this: through the Circle dashboard or programmatically using the Circle SDK.

The former is extremely simple. Visit the Circle developer dashboard and see the status of your payment.


For the programmatic approach (which is what you’ll likely use when dealing with thousands of payments), let’s write a script in a new file called confirmation.js.

const { Circle, CircleEnvironments } = require("@circle-fin/circle-sdk");
const circle = new Circle( 'SAND_API_KEY:a772d01fa660452b4fbf2d62b15692d0:ee43119fbf2e4c0677921f22f5ae66da', CircleEnvironments.sandbox ); async function getPayment(paymentId) { const resp = await circle.payments.getPayment(paymentId); console.log(; } async function getPaymentIntent() { const paymentIntentId = '76075284-212d-4268-9dfe-4780a6744beb'; const paymentIntent = await circle.cryptoPaymentIntents.getPaymentIntent(paymentIntentId); const paymentIds = let paymentId; // If payment has been made, paymentsId will be a non-empty list if (paymentIds.length > 0) { paymentId = paymentIds[0] } else { console.log("Payment hasn't been made yet!") return } getPayment(paymentId); } getPaymentIntent();

This code should be familiar to you from the last step. All we’re doing here is accessing the payment intent by ID and checking if a payment has been made.

Run the confirmation script using:

$ node confirmation.js

You should see an output that looks something like this:

  data: {
    id: 'babec85e-00b0-374c-ad6f-a854218cccbd',
    type: 'payment',
    status: 'paid',
    amount: { amount: '1.00', currency: 'USD' },
    fees: { amount: '0.01', currency: 'USD' },
    createDate: '2023-08-13T12:39:29.455894Z',
    updateDate: '2023-08-13T12:43:07.156878Z',
    merchantId: 'bfdb53c3-9e63-4116-b3da-bf357e78bbba',
    merchantWalletId: '1016320978',
    paymentIntentId: '76075284-212d-4268-9dfe-4780a6744beb',
    settlementAmount: { amount: '1.00', currency: 'USD' },
    fromAddresses: { chain: 'ETH', addresses: [Array] },
    depositAddress: {
      chain: 'ETH',
      address: '0x0d53d534bb7bb10b7bfe50095a113a579759900a'
    transactionHash: '0x52df384329672112a2782715187409bbe8ad6fa7a6670b24b3a1c14a8f4d3cb4'

Accessing data[status] and checking if it is paid will allow you to code any custom logic from here.


And there you have it! You’ve successfully set up a modern payments infrastructure using Circle and USDC. You can now empower your customers around the world to make payments that are easy, fast, and low-cost.  Ready to get started? Our free Circle Mint account enables eligible enterprise developers to directly access USDC from Circle and experience the benefits of near instant and cost effective payments.

circle mint

Back to top