> ## Documentation Index
> Fetch the complete documentation index at: https://dev.moonpay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# IP matching

> Bind a signed on-ramp widget URL to a hash of the customer's IP address so a generated URL only loads for the intended device.

IP matching is a security upgrade for the on-ramp widget, and is **mandatory for going live.** On top of [signing your widget URLs](/widget/on-ramp/customization/url-signing), you bind each URL to a hash of the customer's IP address at the point you generate it. When the widget loads, MoonPay hashes the IP address it observes and compares it to the value signed into the URL. If the observed IP address does not match the hash, the widget shows an error and does not load.

This protects against intercepted or stolen URLs being used by anyone other than the intended customer.

<Note>
  IP matching is required to go live with the on-ramp widget. Build and test
  your integration in sandbox first. MoonPay confirms enforcement is active on
  your account before you go live.
</Note>

## Requirements

A widget URL passes IP matching when both of the following are true:

* The URL is signed with your secret key. See [URL signing](/widget/on-ramp/customization/url-signing).
* The URL includes an `allowedIpAddress` parameter set to a hash of the customer's IP address, and that hash is part of the signed query string.

## Before you begin

* Get your secret key from the [Developers > API Keys](https://dashboard.moonpay.com/developers/api-keys) page of your MoonPay dashboard. Keep it on your backend and never expose it client-side.
* Make sure your backend can capture the customer's live, public IP address at the point of URL generation.

## How it works

```mermaid theme={null}
sequenceDiagram
    participant C as Customer device
    participant P as Your backend
    participant M as MoonPay
    C->>P: Request widget URL
    Note over P: Capture the customer's public IP
    Note over P: Hash the IP with your secret key (HMAC-SHA256)
    Note over P: Append allowedIpAddress and sign the query string
    P-->>C: Return the signed URL
    C->>M: Open the widget with the signed URL
    Note over M: Hash the observed IP and compare to allowedIpAddress
    alt Hashes match
        M-->>C: Widget loads
    else Hashes do not match
        M-->>C: Widget shows an error and does not load
    end
```

## Generate a signed URL with IP matching

Build the URL on your backend in two steps: hash the customer's IP address and add it as `allowedIpAddress`, then sign the full query string.

### Step 1: Hash the customer's IP address

Capture the customer's public IP address, hash it with your secret key using HMAC-SHA256, and append the result to the widget URL as the `allowedIpAddress` parameter.

```typescript Node.js theme={null}
import crypto from "crypto";

const secretKey = "sk_live_key"; // Use your secret key

// The customer's public IP, captured and canonicalized on your backend.
const customerIp = "203.0.113.42";

const ipHash = crypto
  .createHmac("sha256", secretKey)
  .update(customerIp)
  .digest("base64");

const baseUrl =
  "https://buy.moonpay.com?apiKey=pk_live_key&currencyCode=eth&walletAddress=0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae";

const urlWithIp = `${baseUrl}&allowedIpAddress=${encodeURIComponent(ipHash)}`;
```

<Warning>
  A mismatch blocks the widget from loading. A customer's IP address can change
  between the point you generate the URL and the point they open it, for example
  on a VPN, a proxy, a corporate network, or a mobile device switching between
  Wi-Fi and cellular. Generate the signed URL as close as possible to when the
  customer opens the widget.
</Warning>

To capture the right IP address:

* Use the IP address of the device that will open the widget, such as the `True-Client-IP` header or the left-most `X-Forwarded-For` value behind your proxy or CDN.
* Do not use a server-side or internal address.
* Canonicalize the IP address to a stable string before hashing. HMAC is sensitive to any formatting difference, so the value you hash must exactly match the value MoonPay observes.

### Step 2: Sign the URL

Sign the full query string, including `allowedIpAddress`, with the same secret key. Append the result as the `signature` parameter.

```typescript Node.js theme={null}
const signature = crypto
  .createHmac("sha256", secretKey)
  .update(new URL(urlWithIp).search)
  .digest("base64");

const urlWithSignature = `${urlWithIp}&signature=${encodeURIComponent(signature)}`;

console.log(urlWithSignature);
```

For the full signing flow, including SDK-based signing and PHP examples, see [URL signing](/widget/on-ramp/customization/url-signing).

## What to expect

| Scenario        | Condition                                                                          | Result                                                                                                                 |
| --------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Matching IP     | The signature is valid and the hash of the observed IP matches `allowedIpAddress`. | The customer proceeds through the flow.                                                                                |
| Non-matching IP | The signature is valid but the hashes do not match.                                | The widget fails to load and the customer cannot continue. MoonPay logs the observed client IP to help with debugging. |

When the IP address does not match, the customer sees an "Unverified Connection" error in place of the widget:

> **Unverified Connection**
>
> We couldn't verify this request. If this continues, contact support through the official MoonPay app or website.

## Testing

Test your implementation in sandbox before you turn it on in production, using your sandbox API key from the [Developers > API Keys](https://dashboard.moonpay.com/developers/api-keys) page of your MoonPay dashboard.

## Next steps

1. Test your implementation in sandbox and confirm the widget loads for matching IP addresses and returns an error for mismatches.
2. Notify your MoonPay integration contact once testing is complete.
3. MoonPay confirms enforcement is active on your account before you go live.
