# Subscriptions with custom checkout

Collect payment details directly on your site using [MONEI Components](https://docs.monei.com/monei-js/overview/.md) to activate subscriptions. This gives you full control over the checkout experience.

## Before You Begin[​](#before-you-begin "Direct link to Before You Begin")

* You need a MONEI account, your [API key](https://dashboard.monei.com/settings/api), and your **Account ID** (found in [Dashboard settings](https://dashboard.monei.com/settings/api)).
* Use your [test mode keys](https://docs.monei.com/testing/.md) during development.
* Ensure at least one [payment method is enabled](https://dashboard.monei.com/settings/payment-methods) in your account settings.

## Choose Your Flow[​](#choose-your-flow "Direct link to Choose Your Flow")

There are two ways to activate a subscription with MONEI Components:

|                    | Token first                                                    | Activate first                                                |
| ------------------ | -------------------------------------------------------------- | ------------------------------------------------------------- |
| **How it works**   | Collect payment details → generate token → activate with token | Activate → get payment ID → collect details → confirm payment |
| **When to use**    | Simpler flow, fewer server roundtrips                          | When you need the payment object before collecting details    |
| **Component init** | `accountId` + `sessionId`                                      | `paymentId`                                                   |

## Token first (recommended)[​](#token-first-recommended "Direct link to Token first (recommended)")

Generate a payment token on the client, then activate the subscription server-side with that token.

<!-- -->

<!-- -->

### 1. Create a subscription (Server-side)[​](#1-create-a-subscription-server-side "Direct link to 1. Create a subscription (Server-side)")

Create a [Subscription](https://docs.monei.com/apis/rest/subscriptions-create/.md) on your server. This step is identical to the [prebuilt payment page](https://docs.monei.com/subscriptions/use-prebuilt-payment-page/.md#1-create-a-subscription-server-side) flow.

* cURL
* Node.js
* PHP
* Python

POST https\://api.monei.com/v1/subscriptions

```
curl --request POST 'https://api.monei.com/v1/subscriptions' \

--header 'Authorization: YOUR_API_KEY' \

--header 'Content-Type: application/json' \

--data-raw '{

  "amount": 1500,

  "currency": "EUR",

  "interval": "month",

  "intervalCount": 1,

  "description": "Pro Plan Monthly",

  "customer": {

    "name": "John Doe",

    "email": "john.doe@example.com"

  },

  "callbackUrl": "https://example.com/subscriptions/callback",

  "paymentCallbackUrl": "https://example.com/payments/callback"

}'
```

server.js

```
import {Monei} from '@monei-js/node-sdk';



const monei = new Monei('YOUR_API_KEY');



const subscription = await monei.subscriptions.create({

  amount: 1500,

  currency: 'EUR',

  interval: 'month',

  intervalCount: 1,

  description: 'Pro Plan Monthly',

  customer: {

    name: 'John Doe',

    email: 'john.doe@example.com'

  },

  callbackUrl: 'https://example.com/subscriptions/callback',

  paymentCallbackUrl: 'https://example.com/payments/callback'

});



const subscriptionId = subscription.id;
```

server.php

```
<?php

require_once 'vendor/autoload.php';



use Monei\MoneiClient;

use Monei\Model\CreateSubscriptionRequest;

use Monei\Model\PaymentCustomer;



$monei = new MoneiClient('YOUR_API_KEY');



$subscription = $monei->subscriptions->create(

  new CreateSubscriptionRequest([

    'amount' => 1500,

    'currency' => 'EUR',

    'interval' => 'month',

    'interval_count' => 1,

    'description' => 'Pro Plan Monthly',

    'customer' => new PaymentCustomer([

      'name' => 'John Doe',

      'email' => 'john.doe@example.com'

    ]),

    'callback_url' => 'https://example.com/subscriptions/callback',

    'payment_callback_url' => 'https://example.com/payments/callback'

  ])

);



$subscriptionId = $subscription->getId();

?>
```

server.py

```
import Monei

from Monei import CreateSubscriptionRequest, PaymentCustomer



monei = Monei.MoneiClient(api_key="YOUR_API_KEY")



subscription = monei.subscriptions.create(

    CreateSubscriptionRequest(

        amount=1500,

        currency="EUR",

        interval="month",

        interval_count=1,

        description="Pro Plan Monthly",

        customer=PaymentCustomer(

            name="John Doe",

            email="john.doe@example.com"

        ),

        callback_url="https://example.com/subscriptions/callback",

        payment_callback_url="https://example.com/payments/callback"

    )

)



subscription_id = subscription.id
```

### 2. Add Component to your page (Client-side)[​](#2-add-component-to-your-page-client-side "Direct link to 2. Add Component to your page (Client-side)")

Include `monei.js` on your checkout page and mount the CardInput component using your `accountId` and a unique `sessionId`.

checkout.html

```
<head>

  <title>Checkout</title>

  <script src="https://js.monei.com/v3/monei.js"></script>

</head>

<body>

  <div id="card-element">

    <!-- MONEI Card Input Component will be inserted here -->

  </div>

  <button id="subscribe-button">Subscribe</button>

  <script src="client.js"></script>

</body>
```

client.js

```
// Initialize CardInput with your accountId and a unique sessionId

const cardElement = monei.CardInput({

  accountId: 'YOUR_ACCOUNT_ID',

  sessionId: 'unique_session_id' // Unique per customer session

});



// Render the Component into the container

cardElement.render('#card-element');
```

important

Use a different `sessionId` for each customer session. This ensures the token-generating customer matches the paying customer. You must pass the **same `sessionId`** when activating the subscription in step 4.

### 3. Get the payment token (Client-side)[​](#3-get-the-payment-token-client-side "Direct link to 3. Get the payment token (Client-side)")

When the customer clicks the subscribe button, submit the card input to generate a one-time `paymentToken`.

client.js

```
document.getElementById('subscribe-button').addEventListener('click', async () => {

  // Generate a payment token from the card input

  const {token, error} = await cardElement.submit();



  if (error) {

    // Show the error to the customer

    console.error('Error:', error);

    return;

  }



  // Send the token and sessionId to your server

  const response = await fetch('/activate-subscription', {

    method: 'POST',

    headers: {'Content-Type': 'application/json'},

    body: JSON.stringify({

      subscriptionId: '{{subscription_id}}',

      paymentToken: token,

      sessionId: 'unique_session_id'

    })

  });



  const result = await response.json();

  // Handle the result (e.g., show success message)

});
```

### 4. Activate with token (Server-side)[​](#4-activate-with-token-server-side "Direct link to 4. Activate with token (Server-side)")

Activate the subscription with the `paymentToken` and `sessionId` received from the client.

* cURL
* Node.js
* PHP
* Python

POST https\://api.monei.com/v1/subscriptions/{id}/activate

```
curl --request POST 'https://api.monei.com/v1/subscriptions/YOUR_SUBSCRIPTION_ID/activate' \

--header 'Authorization: YOUR_API_KEY' \

--header 'Content-Type: application/json' \

--data-raw '{

  "paymentToken": "token_from_client",

  "sessionId": "unique_session_id"

}'
```

server.js

```
// In your /activate-subscription route handler

app.post('/activate-subscription', async (req, res) => {

  const {subscriptionId, paymentToken, sessionId} = req.body;



  const activation = await monei.subscriptions.activate(subscriptionId, {

    paymentToken,

    sessionId

  });



  res.json({status: activation.status});

});
```

server.php

```
<?php

use Monei\Model\ActivateSubscriptionRequest;



$body = json_decode(file_get_contents('php://input'), true);



$activation = $monei->subscriptions->activate(

  $body['subscriptionId'],

  new ActivateSubscriptionRequest([

    'payment_token' => $body['paymentToken'],

    'session_id' => $body['sessionId']

  ])

);



echo json_encode(['status' => $activation->getStatus()]);

?>
```

server.py

```
from Monei import ActivateSubscriptionRequest



# In your /activate-subscription route handler

activation = monei.subscriptions.activate(

    body["subscriptionId"],

    ActivateSubscriptionRequest(

        payment_token=body["paymentToken"],

        session_id=body["sessionId"]

    )

)
```

### 5. Handle webhooks (Server-side)[​](#5-handle-webhooks-server-side "Direct link to 5. Handle webhooks (Server-side)")

Webhook handling is the same as the [prebuilt payment page](https://docs.monei.com/subscriptions/use-prebuilt-payment-page/.md#5-handle-webhooks-server-side) flow. MONEI sends payment callbacks to `paymentCallbackUrl` and subscription status changes to `callbackUrl`. Always [verify the signature](https://docs.monei.com/guides/verify-signature/.md) and return a `200` status code.

***

## Activate first (alternative)[​](#activate-first-alternative "Direct link to Activate first (alternative)")

Activate the subscription first to get a payment ID, then collect payment details and confirm client-side.

<!-- -->

### 1. Create a subscription[​](#1-create-a-subscription "Direct link to 1. Create a subscription")

Same as step 1 above.

### 2. Activate the subscription (Server-side)[​](#2-activate-the-subscription-server-side "Direct link to 2. Activate the subscription (Server-side)")

Activate without a `paymentToken` — the response is a [Payment object](https://docs.monei.com/apis/rest/schemas/payment/.md) with a `payment.id`.

* cURL
* Node.js
* PHP
* Python

POST https\://api.monei.com/v1/subscriptions/{id}/activate

```
curl --request POST 'https://api.monei.com/v1/subscriptions/YOUR_SUBSCRIPTION_ID/activate' \

--header 'Authorization: YOUR_API_KEY' \

--header 'Content-Type: application/json' \

--data-raw '{

  "completeUrl": "https://example.com/checkout/complete"

}'
```

server.js

```
const activation = await monei.subscriptions.activate('YOUR_SUBSCRIPTION_ID', {

  completeUrl: 'https://example.com/checkout/complete'

});



// Pass the payment ID to your client-side

const paymentId = activation.id;
```

server.php

```
<?php

use Monei\Model\ActivateSubscriptionRequest;



$activation = $monei->subscriptions->activate(

  'YOUR_SUBSCRIPTION_ID',

  new ActivateSubscriptionRequest([

    'complete_url' => 'https://example.com/checkout/complete'

  ])

);



// Pass the payment ID to your client-side

$paymentId = $activation->getId();

?>
```

server.py

```
from Monei import ActivateSubscriptionRequest



activation = monei.subscriptions.activate(

    "YOUR_SUBSCRIPTION_ID",

    ActivateSubscriptionRequest(

        complete_url="https://example.com/checkout/complete"

    )

)



# Pass the payment ID to your client-side

payment_id = activation.id
```

important

Use the `payment.id` from the response to initialize the CardInput component — do **not** redirect the customer to `nextAction.redirectUrl`. That URL is for the [prebuilt payment page](https://docs.monei.com/subscriptions/use-prebuilt-payment-page/.md) flow.

### 3. Mount Component and confirm payment (Client-side)[​](#3-mount-component-and-confirm-payment-client-side "Direct link to 3. Mount Component and confirm payment (Client-side)")

Initialize the CardInput with the `paymentId` from the activation response, then confirm the payment.

checkout.html

```
<head>

  <title>Checkout</title>

  <script src="https://js.monei.com/v3/monei.js"></script>

</head>

<body>

  <div id="card-element"></div>

  <button id="subscribe-button">Subscribe</button>

  <script src="client.js"></script>

</body>
```

client.js

```
// Initialize CardInput with the paymentId from the activation response

const paymentId = '{{payment_id}}'; // Passed from your server

const cardElement = monei.CardInput({paymentId: paymentId});

cardElement.render('#card-element');



document.getElementById('subscribe-button').addEventListener('click', async () => {

  const {token, error} = await cardElement.submit();



  if (error) {

    console.error('Error:', error);

    return;

  }



  // Confirm the payment with the generated token

  const result = await monei.confirmPayment({

    paymentId: paymentId,

    paymentToken: token

  });



  // Show the result to the customer

  // Always rely on webhooks for the definitive payment status

  console.log('Payment status:', result.status);

});
```

### 4. Handle webhooks[​](#4-handle-webhooks "Direct link to 4. Handle webhooks")

Same as the [token first flow](#5-handle-webhooks-server-side) — MONEI sends webhooks for both payments and subscription status changes.

## Before You Go Live[​](#before-you-go-live "Direct link to Before You Go Live")

* Switch to your [live mode](https://dashboard.monei.com/settings/api) API key and Account ID.
* Ensure your [payment methods are enabled](https://dashboard.monei.com/settings/payment-methods) in live mode.
* Test your webhook endpoints handle both payment and subscription callbacks correctly.

Test mode limits

In test mode, subscriptions have the following limits:

* Maximum **3 active subscriptions** per account
* Subscriptions are **auto-canceled after 12 payments**
* **Minute and hour** billing intervals are available for faster testing
