# How to Accept Crypto Payments on Your Website

> A practical guide to adding crypto payments to your website or app. From choosing a gateway to writing your first API call.

Source: https://mutopay.com/blog/how-to-accept-crypto-payments/
Language: en
Date: 2026-03-17

---

So you want to accept crypto on your website. Good news: it's simpler than you think. Bad news: most guides overcomplicate it. Here's the no-fluff version.

## What You Actually Need

Forget running your own node or managing wallets manually. A crypto payment gateway handles the hard parts: generating payment addresses, monitoring blockchains for confirmations, and converting tokens.

You need three things:
1. A wallet address to receive funds
2. A payment gateway with an API
3. About 20 minutes

## Step 1: Choose Your Settlement Currency

This is the first decision that matters. Do you want to receive ETH? USDC? A mix?

Most merchants go with stablecoins (USDC, USDT, DAI) because the value doesn't swing 10% overnight. With MutoPay, your customers can pay with any token, but you always receive your chosen stablecoin. Problem solved before it starts.

## Step 2: Create a Channel and Get Your API Key

In your [MutoPay dashboard](/dashboard), go to Settings → Channels and click **+ New channel**. Give it a name (e.g. "My Website") and optionally set a webhook URL. Your API key (`ep_...` prefix) is shown once. Copy it.

This key identifies your channel and authenticates your API calls. Each channel has its own key, webhook URL, and can have its own settlement destination.

## Step 3: Create a Payment

One API call. That's it.

```bash
curl -X POST https://mutopay.com/api/payments \
  -H "X-API-Key: ep_your_channel_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "amount_usd": 50.00,
    "description": "Order #1234: Blue Widget",
    "external_id": "order_1234",
    "callback_url": "https://yoursite.com/order/1234/complete"
  }'
```

You get back a payment object with an `id`. Redirect your customer to `mutopay.com/pay/{id}`.

**Fields you can set:**

| Field | Description |
|-------|-------------|
| `amount_usd` | Amount in USD |
| `amount` | Amount in another currency (use with `currency`) |
| `currency` | Currency code if not USD (e.g. `EUR`, `GBP`) |
| `description` | Shown to the customer on the payment page |
| `external_id` | Your internal reference (e.g. order ID) |
| `callback_url` | Where to redirect the customer after payment |
| `metadata` | Arbitrary JSON for your own use |
| `expires_in_minutes` | 15 to 10080 (7 days); default is 60 |

Use either `amount_usd` or `amount` + `currency`, not both.

## Step 4: Handle the Webhook

When the payment completes (or fails, or expires), MutoPay sends a POST to your channel's webhook URL. The payload tells you everything:

```json
{
  "event": "payment.completed",
  "payment_id": "pay_abc123",
  "status": "completed",
  "amount_usd": 50.00,
  "currency": "USD",
  "dest_token": "USDC",
  "dest_chain_id": "137",
  "dest_amount": "50000000",
  "dest_decimals": 6,
  "external_id": "order_1234",
  "tx_hash": "0xabc...",
  "completed_at": "2026-04-12T14:30:00Z",
  "timestamp": "2026-04-12T14:30:01Z"
}
```

**Verify the signature.** Every webhook includes an `X-MutoPay-Signature` header with an HMAC-SHA256 hash of the raw JSON body, signed with your channel's webhook secret (visible in your dashboard). Format: `sha256=<hex>`.

**Parse the settlement amount.** `dest_amount` is in raw token units. Divide by `10^dest_decimals` to get the human-readable figure. In the example above: `50000000 / 10^6 = 50.00 USDC`.

Webhook events: `payment.completed`, `payment.failed`, `payment.expired`, `payment.underpaid`, `payment.kyc_required`, `payment.needs_manual_check`.

Failed deliveries are retried 5 times with exponential backoff (1 min → 5 min → 30 min → 2 hours → 12 hours).

## Step 5: Show the Customer a Payment Page

You have two options:

**Redirect**: send the customer to the hosted payment page at `mutopay.com/pay/{id}`. They select their token, connect their wallet, and pay. Zero frontend work on your end. Mobile customers see a QR code for easy deposit.

**Poll**: use the payment ID to build your own UI. Call `GET /api/payments/{id}/status` for lightweight status updates.

Most merchants start with the redirect approach and customize later.

## Common Questions

**What if the customer pays with a token on a different chain?**
The gateway handles cross-chain bridging automatically. Customer pays with ARB on Arbitrum, you receive USDC on Polygon. They don't need to think about it.

**What about refunds?**
Crypto payments are irreversible at the protocol level. Handle refunds by sending a separate transaction back to the customer's wallet.

**How long do payments take?**
Direct stablecoin transfers confirm in seconds. Cross-chain swaps typically settle in under 2 minutes.

**What's the fee?**
MutoPay charges 0.5% per transaction. No monthly fees, no minimums.

**What chains are supported?**
Ethereum, Polygon, Arbitrum, Base, Optimism, Avalanche, BSC, TON, Solana, and Tron. Customers can pay with 1,000+ tokens across all 10 chains.

## What's Next

Once you're processing payments, you'll want to:
- Monitor payments in your [merchant dashboard](/dashboard)
- Test your webhook integration from Settings → Channels → Test Webhook
- Add a [Pay Me link](/blog/pay-me-links) for tips and open-ended payments
- Explore [channel management](/blog/channel-management) for multi-brand or multi-client setups

The whole integration takes about 20 minutes for a developer who's done API work before. No blockchain knowledge required.
