# Take USDC and USDT payments in your app

> Take stablecoin payments with no change to your checkout code: switch them on, fulfil on payment.succeeded, and keep the on-chain transaction with each order.

Source: https://anytime-capital.com/docs/accept-stablecoins · Markdown: https://anytime-capital.com/docs/accept-stablecoins.md · All docs: https://anytime-capital.com/docs/llms-full.txt

If your integration already creates [payment links](https://anytime-capital.com/docs/payment-links.md) or [invoices](https://anytime-capital.com/docs/invoices.md) and fulfils on the `payment.succeeded` webhook, it takes stablecoins as it is: the customer picks **Pay with stablecoin** on the same checkout, sends USDC or USDT from their wallet straight to your business wallet, and you get the same event once the transfer is final. This guide adds the on-chain details to your orders. How matching, confirmations and statuses work is on [Stablecoin checkout](https://anytime-capital.com/docs/stablecoin-checkout.md).

## 1. Switch it on

1. Set up your business wallet on the **Wallets** page.
2. In **Settings → Stablecoins** (owners and admins), turn on **Accept stablecoins**, pick tokens and networks, and confirm the receiving address for each.
3. Your links and invoices now offer **Pay with stablecoin** below the card form. Nothing changes in your code.

> **Where it isn't offered:** Prices stay in US dollars, paid at 1 token = $1. Stablecoins aren't offered on subscriptions, on links that collect a shipping address or ask required questions, or while sales tax is added at checkout.

## 2. Fulfil on payment.succeeded

A stablecoin payment's `payment.succeeded` has `paymentMethod.type` `stablecoin`, an id starting `pay_sc`, and a `stablecoin` object with the chain, token, amounts, wallets and each transaction's explorer link. The `stablecoin` object comes with the webhook only: `GET /api/v1/payments/{id}` returns the payment without it, so store what you need when the event arrives.

**webhook.mjs**

```javascript
// webhook.mjs — Node.js 18+ with Express: npm install express
import crypto from "node:crypto";
import express from "express";

const app = express();
const secret = process.env.ANYTIME_WEBHOOK_SECRET; // whsec_…

function verify(rawBody, header) {
  const parts = Object.fromEntries(String(header ?? "").split(",").map((part) => part.split("=")));
  const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  return (
    typeof parts.v1 === "string" &&
    parts.v1.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected)) &&
    Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300
  );
}

app.post("/webhooks/anytime", express.raw({ type: "application/json" }), async (req, res) => {
  const rawBody = req.body.toString("utf8");
  if (!verify(rawBody, req.get("Anytime-Signature"))) return res.status(400).send("Invalid signature");
  const event = JSON.parse(rawBody);
  // Skip event.id if you've handled it before.

  if (event.type === "payment.succeeded") {
    const payment = event.data;
    const order = { id: payment.metadata.orderId, paymentId: payment.id, paidCents: payment.amountCents };

    if (payment.paymentMethod?.type === "stablecoin" && payment.stablecoin) {
      // Final on-chain: keep the proof with the order.
      order.method = payment.paymentMethod.label; // "USDC on Ethereum"
      order.txHash = payment.stablecoin.txHash;
      order.explorerUrl = payment.stablecoin.explorerUrl;
      order.tokenAmount = payment.stablecoin.amount; // what arrived, e.g. "25.004217"
    }
    await markPaid(order); // your database
  }
  res.sendStatus(200);
});

async function markPaid(order) {
  console.log("paid", order);
}

app.listen(3000);
```

## 3. Refunds and mistakes

- Refunds are manual: the tokens went to your own wallet, so send them back from it, in the same token on the same network. The [refunds API](https://anytime-capital.com/docs/refunds.md) doesn't refund stablecoin payments.
- An overpayment counts as paid and you're told the difference to send back; an expired or underpaid one tells you what arrived. Both show in the dashboard, not as webhooks.
- Only `payment.succeeded` is sent: there's no event for a stablecoin payment that never arrives, so let unpaid orders time out on your side as you would for an abandoned card checkout.

---

Anytime Capital is a fintech company, not a bank. Payment processing, banking and payout services are provided by Anytime Capital's licensed partners. Rates shown are for US card payments; international cards, currency conversion and disputes may carry additional fees shown in your dashboard before you accept a payment.
