# API Quickstart: invoice → webhook

This quickstart walks through a minimal, end-to-end integration path:

1. Prepare signature-based API key authentication
2. Create an invoice
3. Redirect buyer to the payment page
4. Verify invoice webhooks

---

## Prerequisites

- Base URL: `https://api.coinssend.com`
- A merchant ID from your CoinsSend dashboard
- A merchant API key for signature auth (`Merchant` + `Sign` headers; withdrawals also require `Timestamp`)

References:

- [Authentication](./authentication.md)
- [OpenAPI contract](./openapi.json)
- [Signature test vectors](./signature-test-vectors.json)
- [Invoices](./invoices.md)
- [Webhooks](./webhooks.md)

> Want a coding assistant to build this flow? Use the full checkout prompt in [AI Integration Prompts](./ai-integration-prompts.md) after reviewing these steps.

---

## Step 1) Prepare API key signature auth for merchant endpoints

Get your merchant ID and API key from the CoinsSend dashboard before making public API requests.

For signed endpoints (for example `POST /v1/invoices`), generate:

```text
canonical_body = json_encode(json_decode(request_body)) using PHP defaults
Sign = md5(base64(canonical_body) + API_KEY)
```

Withdrawal requests use a stricter timestamped signature instead:

```text
Timestamp = current Unix timestamp in seconds
Sign = hmac_sha256(base64(canonical_body) + "." + Timestamp, API_KEY)
```

Send `canonical_body` as the request body. PHP's default JSON encoding escapes
slashes and non-ASCII characters, so plain `JSON.stringify()` output is not
always equivalent.

Example in Node.js:

```javascript
import { createHash } from 'node:crypto';

function canonicalJson(value) {
  return JSON.stringify(value)
    .replace(/\//g, '\\/')
    .replace(/[\u0080-\uFFFF]/g, (character) =>
      `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`
    );
}

const body = canonicalJson({
  order_id: 'order_10001',
  amount: '100.50',
  is_customer_fee: true,
  allow_card_payments: true,
  success_url: 'https://merchant.example/pay/success',
  cancel_url: 'https://merchant.example/pay/cancel'
});

const sign = createHash('md5')
  .update(Buffer.from(body).toString('base64') + process.env.COINSSEND_API_KEY)
  .digest('hex');
```

Required headers for signed merchant API calls:

- `Merchant: <MERCHANT_ID>`
- `Sign: <computed_md5_signature>`
- `Content-Type: application/json`

For `POST /v1/withdrawals`, send `Timestamp: <unix_timestamp_seconds>` and set `Sign` to the HMAC-SHA256 value.

---

## Step 2) Create an invoice (API key auth)

```bash
BODY='{"order_id":"order_10001","amount":"100.50","is_customer_fee":true,"allow_card_payments":true,"success_url":"https:\/\/merchant.example\/pay\/success","cancel_url":"https:\/\/merchant.example\/pay\/cancel"}'
# BODY is canonical PHP-compatible JSON. Compute SIGN from these exact bytes.
SIGN='<computed_md5_signature>'

curl -X POST "https://api.coinssend.com/v1/invoices" \
  -H "Content-Type: application/json" \
  -H "Merchant: $MERCHANT_ID" \
  -H "Sign: $SIGN" \
  -d "$BODY"
```

Example response:

```json
{
  "status": "success",
  "data": {
    "id": "4e7d786f-a4a6-4a42-b0e5-6f2bb8a5f0f2",
    "order_id": "order_10001",
    "url": "https://pay.coinssend.com/invoice_code",
    "code": "invoice_code",
    "amount": "100.50",
    "payer_amount": "100.50",
    "expired_at": "2026-04-03T12:00:00+00:00",
    "created_at": "2026-04-02T12:00:00+00:00"
  }
}
```

---

## Step 3) Send buyer to payment URL

Use `data.url` from invoice creation response as the checkout URL.

- Typical shape: `https://pay.coinssend.com/{invoice_code}`
- `data.code` can also be used with public invoice lookup: `GET /v1/invoices/{invoiceCode}`
- Direct crypto payment is always available. Card checkout is shown only when `allow_card_payments` is `true` and the invoice amount or remaining amount is at least `$15.00` / `1500` cents.
- `allow_card_payments` and `provider_card` can appear in invoice lookup and payment-page invoice payloads, but they are not part of the `POST /v1/invoices` create response.
- Redirects from success or cancel URLs, provider redirects, provider webhooks, and provider status polling are only UX signals or provider hints. Confirm payment from invoice status changes after chain confirmation, usually through signed webhooks.

---

## Step 4) Handle webhook events

When invoice/payment state changes, CoinsSend sends signed webhook callbacks to your webhook URL.

Common events for this flow:

- `invoice.paid`
- `invoice.expired`
- `aml.rejected.invoice` (when AML blocks invoice deposit)

Verify `X-Signature` using your API key and the **raw** request body bytes:

```php
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $payload, $apiKey);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('invalid signature');
}
```

## Next steps

- Expand invoice validation and error handling: [Invoices](./invoices.md)
- Add duplicate-safe processing in your webhook consumer: [Webhooks](./webhooks.md)
- Do not blindly retry invoice creation: the endpoint has no idempotency-key contract. Reconcile by `order_id` before deciding whether to create again.
- Review request signing details: [Authentication](./authentication.md)
