# Quickstart

> Create your first invoice in the sandbox, simulate a payment, and receive the signed webhook.

This guide takes you from zero to a paid test invoice in the [sandbox](https://docs.cryptonly.net/guides/sandbox-environment.md). You will create an invoice through the API, pay it without sending any crypto, and receive the `invoice.statusChanged` webhook on your server.

You need a sandbox Cryptonly Merchant Dashboard account and an HTTPS endpoint that can receive webhooks. For local development, expose your server with a tunnel such as ngrok or Cloudflare Tunnel.

## 1. Get your credentials

1. Log in to the [sandbox Cryptonly Merchant Dashboard](https://sandbox-merchant.cryptonly.net). No account yet? [Create one](https://sandbox-merchant.cryptonly.net/sign-up).
2. Go to **Accounts**, select the account, and open its **Settings → Security** tab.
3. Create an API key with the **invoices** scope. The key is bound to this account, so you never pass an account ID in requests. The secret is shown only once. See [Authorization](https://docs.cryptonly.net/api-reference/authorization.md).
4. On the same page, rotate the **Webhook signing key** and copy it. You will use it to verify webhooks.

Store both secrets in your server environment:

```bash
CRYPTONLY_API_KEY=your_sandbox_api_key
CRYPTONLY_WEBHOOK_SECRET=your_webhook_signing_key
```

## 2. Create an invoice

Send `POST /invoice` to the sandbox API at `https://sandbox-api-merchant.cryptonly.net`. `orderId` is your own reference and the [idempotency key](https://docs.cryptonly.net/api-reference/idempotency.md), so use a new value for each invoice.

<Tabs>
<TabItem label="cURL">

```bash
curl -X POST https://sandbox-api-merchant.cryptonly.net/invoice \
  -H "x-tenant-api-key: $CRYPTONLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 25,
    "fiatCurrencyCode": "USD",
    "orderId": "order_1001",
    "description": "Order #1001",
    "webhookUrl": "https://your-server.example.com/webhooks/cryptonly"
  }'
```

</TabItem>
<TabItem label="@cryptonly/sdk">

```ts

const client = new Cryptonly({
  apiKey: process.env.CRYPTONLY_API_KEY!,
  sandbox: true, // the SDK calls production by default
});

const invoice = await client.invoice.create({
  amount: 25,
  fiatCurrencyCode: 'USD',
  orderId: 'order_1001',
  description: 'Order #1001',
  webhookUrl: 'https://your-server.example.com/webhooks/cryptonly',
});

console.log(invoice.paymentPageUrl);
```

</TabItem>
</Tabs>

The invoice is created on the account the API key belongs to. The response is `201 Created` with the invoice in `created` status. The field you need next is `paymentPageUrl`, the hosted checkout you send to your customer. See the [OpenAPI reference](/api-reference/openapi/) for every request and response field.

You can leave out `webhookUrl` if you set a default invoice webhook URL under **Settings → Integration**.

## 3. Pay the invoice

Open `paymentPageUrl` to see the checkout your customer will see. To complete the payment without testnet funds:

1. In the sandbox dashboard, go to **Accounts → History**.
2. Search for `order_1001` and open the invoice.
3. Click **Simulate payment**.

The invoice moves through `processing` to `paid` through the real settlement pipeline, and your balance is credited. To pay with real testnet coins instead, see [Sandbox environment](https://docs.cryptonly.net/guides/sandbox-environment.md).

## 4. Receive the webhook

Cryptonly sends `invoice.statusChanged` to your `webhookUrl` on every status change: first `created`, then `processing`, then `paid`. Verify the `x-webhook-signature` header against the **raw** request body before trusting it:

```ts

const app = express();

app.post(
  '/webhooks/cryptonly',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.header('x-webhook-signature');
    const result = verifyInvoiceWebhook(req.body, signature, process.env.CRYPTONLY_WEBHOOK_SECRET!);

    if (!result.ok) {
      return res.status(401).end();
    }

    const { data } = result.envelope;
    if (data.status === 'paid') {
      // Mark data.orderId as paid in your system.
    }

    res.status(200).end();
  },
);
```

Respond with any `2xx` status quickly. Failed deliveries are retried for several days. See [Webhooks](https://docs.cryptonly.net/api-reference/webhooks.md) for payloads and examples in Python, Go, PHP, and Java.

## Go live

When everything works in the sandbox, switch to production:

* Use the production dashboard at [merchant.cryptonly.net](https://merchant.cryptonly.net/) and create a new API key and signing key there. Sandbox credentials don't work in production.
* Change the API base URL to `https://api-merchant.cryptonly.net`. With the SDK, remove `sandbox: true`.
* Run through the webhook [security checklist](https://docs.cryptonly.net/api-reference/webhooks.md#security-checklist).

## Next steps

- [Invoices](https://docs.cryptonly.net/guides/invoices.md): Amounts, line items, and the invoice lifecycle
- [Invoice statuses](https://docs.cryptonly.net/guides/invoices/statuses.md): Which statuses to fulfil on, and late payments
- [Webhooks](https://docs.cryptonly.net/api-reference/webhooks.md): Payloads, signatures, and retries
- [Plugins](https://docs.cryptonly.net/integration/plugins.md): Skip the code with a ready-made plugin
