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

# Embedded checkout (V1)

> Open the Roqqett payment journey in a pop-up over your page with the Roqqett JavaScript library and the Roqqett V1 API.

<Warning>
  For existing integrations only. The embedded checkout relies on the deprecated Roqqett V1 API. New integrations should use the [hosted checkout](/pages/roqqett/accept-payments).
</Warning>

With the embedded checkout, the Roqqett JavaScript library opens the payment journey in a pop-up window over your page, and tells your page when the payment is complete. The V2 API has no equivalent: a pop-up payment needs a V1 cart, created with the `transferId` that the library gives you.

The V1 API is on `https://api.roqqett.com`. Authenticate with a Roqqett API key in the `X-API-KEY` header. Amounts are integers in minor units, so `5499` is £54.99. The full reference is under **Roqqett (deprecated)** in the v1 tab.

## Before you begin

In the Roqqett portal at `https://portal.roqqett.com`:

1. Go to **API keys** and create an API key. Treat its secret like a password, and keep it on your server.
2. Go to **Checkouts** and create a checkout. Set its **Default return URL**, and its **Webhook Url** to an `https` endpoint on your server. Keep the checkout's ID.

## How it works

<Steps>
  <Step title="Load the library">
    Your payment page loads the library and creates a Roqqett instance.
  </Step>

  <Step title="The payer clicks your Roqqett button">
    You call `checkout()`. The library opens the Roqqett pop-up and gives you a `transferId`.
  </Step>

  <Step title="Your server creates a cart">
    Your page sends the `transferId` to your server, which creates a cart with `POST /carts`. The pop-up then shows the payment.
  </Step>

  <Step title="The payer pays">
    The payer approves the payment at their bank, in the pop-up or on their phone.
  </Step>

  <Step title="Roqqett tells you the outcome">
    The library calls your callback in the browser, and Roqqett sends a webhook to your server.
  </Step>
</Steps>

## Step 1: Load the library

Add the script to your payment page, and create the Roqqett instance once the page has loaded. Creating the instance adds a hidden 1×1 iframe to the page, which the library uses to talk to Roqqett.

```html theme={null}
<script src="https://pay.roqqett.com/api/channel/fulfilment/js"></script>
<script>
  let roqqett;

  window.addEventListener("load", () => {
    roqqett = Roqqett();
  });
</script>
```

`Roqqett()` takes no arguments. You pass your callbacks to `checkout()`.

## Step 2: Add a Roqqett button

Add a button to your payment page for the payer to choose Roqqett, for example:

```html theme={null}
<button id="pay-with-roqqett" type="button">Pay by bank with Roqqett</button>
```

Match the size and corner radius of your other payment buttons. Roqqett button artwork is in the [Roqqett button pack](https://github.com/EqualsGroup/roqqett-button-packs).

## Step 3: Start the checkout

Call `checkout()` from your button's click handler, before any `await` or network request. The library opens the pop-up as soon as you call it, and browsers block pop-ups that don't open straight from a click. On a desktop, the pop-up offers a QR code or a text message so the payer can finish on their phone.

```javascript theme={null}
document.getElementById("pay-with-roqqett").addEventListener("click", async () => {
  const { transferId, isFresh } = await roqqett.checkout({
    onCompleted: async (event) => {
      // Return true to send the payer to event.data.payload.url.
      return true;
    },
    onCancelled: (event) => {
      // The payer cancelled. They may retry in the pop-up.
    },
    onFailure: (event) => {
      // The payment failed.
    },
  });

  if (isFresh) {
    // Send the transferId to your server, which creates the cart.
    await fetch("/api/roqqett/carts", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ transferId }),
    });
  }
});
```

`checkout(callbacks, isApp)` resolves once the pop-up is ready, with:

* `transferId`: the ID to create the cart with.
* `isFresh`: `true` for a new payment. If the payer clicks again while the pop-up is still open, the library brings the existing pop-up to the front instead. Only create a cart when `isFresh` is `true`.

Pass all three callbacks:

| Callback | When it's called |
| - | - |
| `onCompleted(event)` | The payment is complete, or is pending at the bank. `event.data.payload.url` is the cart's return URL. If you return a truthy value, the library sends the payer there. Return a falsy value to handle navigation yourself. |
| `onCancelled(event)` | The payer cancelled. The pop-up can stay open so they can try another bank, so this isn't always final. |
| `onFailure(event)` | The payment failed. |

Roqqett doesn't add query parameters to the return URL, so set a `returnUrl` on each cart that identifies the order. For a payment that is still pending at the bank, the URL is your return URL with the text `success` replaced by `pending`, if it contains it. Either way, confirm the outcome with the webhook before fulfilling the order.

The library doesn't tell your page when a cart is abandoned. Use the webhook for that.

If your page runs inside your own mobile app's web view, pass `true` as `isApp`. The library then doesn't dim your page behind the pop-up, and the payer is offered a button back to your return URL at the end.

## Step 4: Create the cart

Your server creates the cart with the `transferId`. The library must have registered the `transferId` first, so create the cart only after `checkout()` resolves.

<Note>
  **POST** `https://api.roqqett.com/carts`
</Note>

<CodeGroup>
  ```bash Sample request theme={null}
  curl -i -X POST \
    'https://api.roqqett.com/carts' \
    -H 'X-API-KEY: {apiKey}' \
    -H 'Content-Type: application/json' \
    -d '{
      "checkoutId": "6f1c2d0e-7a4b-4e2f-9c1d-3b5a7e9f0a12",
      "transferId": "9a2e4c1b-7d3f-4b8a-a6e5-0c1d2f3b4a5e",
      "merchantCartId": "ORDER-10421",
      "transaction": {
        "total": { "amount": 5499, "currency": "GBP" },
        "reference": "ORDER10421"
      },
      "basket": {
        "subTotal": 4999,
        "items": [
          {
            "lineId": "1",
            "productId": "SKU-001",
            "productName": "Linen shirt",
            "quantity": 1,
            "unitPrice": 4999,
            "total": 4999,
            "taxRate": 20
          }
        ]
      }
    }'
  ```

  ```json Sample response theme={null}
  {
    "success": true,
    "data": {
      "cartId": "94c92315-53f0-4784-b40d-b7cc3e2f8c73",
      "locale": null
    }
  }
  ```
</CodeGroup>

| Field | Description |
| - | - |
| `checkoutId` | The checkout from the Roqqett portal. |
| `transferId` | The `transferId` from `checkout()`. |
| `merchantCartId` | Your own reference for the cart, such as your order ID. It's shown in the Roqqett portal and sent in webhooks. |
| `transaction.total` | The amount in minor units, in the checkout's currency. |
| `transaction.reference` | The payment reference shown on bank statements. Up to 18 characters, and unique for each payment. |
| `basket` | The items, shown to the payer. Required for shopping checkouts. |
| `returnUrl` | Optional. Overrides the checkout's return URL for this cart. |
| `expiryTimeMinutes` | Optional. How long the payer has to pay. Defaults to 20 minutes. |
| `testMode` | Optional. Set to `true` to [create a test cart](#test-your-integration). |

Keep the `cartId` with your order. To cancel a cart, for example when the payer empties their basket, use `PUT /carts/{cartId}/cancel`.

## Step 5: Handle the webhook

Roqqett posts a JSON body to the checkout's webhook URL when a cart ends. Only fulfil the order once you receive `cart_completed`, not when the payer comes back to your site.

```json theme={null}
{
  "eventType": "cart_completed",
  "cartId": "94c92315-53f0-4784-b40d-b7cc3e2f8c73",
  "merchantCartId": "ORDER-10421",
  "paymentId": "d3579e67-f9f5-4bd8-9e32-a043c91fea24",
  "orderId": "6bcafe48-c6ff-11ec-9d64-0242ac120002",
  "dateTime": "2026-10-07 11:58:04"
}
```

| `eventType` | When |
| - | - |
| `cart_completed` | The payment completed. Includes `paymentId` and `orderId`. |
| `cart_cancelled` | The cart was cancelled, by the payer or by you. |
| `cart_abandoned` | The cart expired without being paid. A payment still pending at the bank when the cart expires isn't abandoned: you get `cart_completed` once the bank confirms it. |
| `order_refunded` | An order was refunded. |
| `ping` | A test sent with the **Ping** button next to the checkout's webhook URL in the Roqqett portal. |

Answer with a `2xx` status once you've stored the event. Roqqett retries cart events a few times when your endpoint fails, and sends `order_refunded` once.

### Verify the webhook

Each webhook has a `Digest` header:

```
Digest: sha-256={signature}
```

The signature is a Base64 RSA-SHA256 signature of the JSON body, with its keys sorted at every level. To verify it, serialise the body you received the same way and check the signature against your public key. Your public key is in the Roqqett portal under **Settings**, on the **Webhooks** tab, and is returned as `publicKey` by `GET /webhooks`.

```javascript theme={null}
import crypto from "node:crypto";
import stringify from "json-stable-stringify";

function isValidRoqqettWebhook(body, digestHeader, publicKeyPem) {
  const signature = digestHeader.replace(/^sha-256=/, "");
  return crypto.verify("sha256", Buffer.from(stringify(body)), publicKeyPem, Buffer.from(signature, "base64"));
}
```

If you set the checkout's **Authentication** to **Basic**, every webhook also carries an `Authorization: Basic` header with the username and password you chose.

The **Ping** button sends three requests: one validly signed, one unsigned and one with an invalid signature. Your endpoint should answer `200` to the first and `401` to the other two.

## Test your integration

V1 carts have a test mode, which takes no money.

1. In the Roqqett portal, tick **Test mode** on the checkout. The checkout still takes real payments.
2. Create the cart with `"testMode": true`.
3. In the pop-up, choose an outcome, such as paid, cancelled or failed, instead of paying at a bank.

Webhooks for test carts include `"testMode": true`.

## Move to the hosted checkout

To move off V1, replace the library and `POST /carts` with a [checkout session](/pages/roqqett/accept-payments), and redirect the payer to its `checkoutUrl`. See [Migrating from Roqqett V1](/pages/roqqett/migrating-from-roqqett-v1).
