# MONEI Connect for developers

Become an integrated payments partner to provide payment services to your platform or marketplace users with [MONEI Connect](https://docs.monei.com/partners-connect/about/.md). Onboard merchants under your brand, operate their accounts on their behalf via the [REST](https://docs.monei.com/apis/rest/.md) and [GraphQL](https://docs.monei.com/apis/graphql/.md) APIs, and embed in-person NFC card payments directly inside your merchant app with the [MONEI Pay SDKs](https://docs.monei.com/monei-pay/app-integration/getting-started/.md).

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

Contact <connect@monei.com> to register as a partner. Include:

* **Your business** — legal name, website, Tax Identification Number (CIF), services description.
* **First admin user** — name, email, phone number. You can add more users later from the dashboard.
* **IPs of your servers** (optional but recommended) so we can restrict API calls to your infrastructure.

Once provisioned, you get access to the **Partner Dashboard** at [admin.monei.com](https://admin.monei.com) and a **registration link** unique to your partner account. Every merchant who signs up via this link is bound to you.

## How it works[​](#how-it-works "Direct link to How it works")

<!-- -->

## API keys[​](#api-keys "Direct link to API keys")

Generate your **Partner API Keys** at [admin.monei.com/settings/api](https://admin.monei.com/settings/api) → **Create API Key**. The page is mode-aware: toggle to live mode for your `pk_live_…` key, test mode for your `pk_test_…` key. Keys can be revoked and re-created from the same page.

## Test mode and live mode[​](#test-mode-and-live-mode "Direct link to Test mode and live mode")

Every partner account is really a **pair**: a live account (`pk_live_…`) and a test account (`pk_test_…`) permanently linked together. Every merchant who signs up through your registration link gets the same twin treatment — one prod sub-account and one test sub-account with different IDs.

This means you can build and test the entire integration — onboarding, API calls, webhooks — in test mode before any merchant completes contract signing.

* **Test sub-accounts are active immediately** after sign-up email confirmation; the merchant does not need to finish onboarding.
* **Live sub-accounts stay `PENDING_CONTRACT`** until the merchant signs the contract and is approved.
* **Live keys cannot call test sub-accounts** (and vice versa). Mode mismatch is the #1 source of 401 errors — see [Common authentication failures](#common-authentication-failures).
* The mode toggle in the dashboard (top-nav) swaps the entire view between live and test sub-accounts.

## Registration link[​](#registration-link "Direct link to Registration link")

The registration link is unique to your partner account. Every merchant who registers through it is bound to you.

```
https://dashboard.monei.com/signup&promo=<PARTNER_CODE>
```

Optionally pass an external ID to identify the merchant in your own system. The ID is echoed back on every webhook.

```
https://dashboard.monei.com/signup&promo=<PARTNER_CODE>&mid=<EXTERNAL_ID>&h=<HASH>
```

* `mid` — your external merchant ID.
* `h` — `HMAC-SHA256(<EXTERNAL_ID>, <PARTNER_API_KEY>)` in hex.

note

Your partner account can be configured to require an external ID. When enabled, the registration link must always include `mid` + `h`.

## Integration[​](#integration "Direct link to Integration")

Send your Partner API Key in the `Authorization` header and the merchant's account ID in the `MONEI-Account-ID` header on every request.

### REST API[​](#rest-api "Direct link to REST API")

Full reference: [REST API](https://docs.monei.com/apis/rest/.md).

* cURL
* Node.js
* PHP
* Python

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

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

--header 'Authorization: <PARTNER_API_KEY>' \

--header 'MONEI-Account-ID: <MONEI_ACCOUNT_ID>' \

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

--header 'User-Agent: MONEI/<PARTNER_NAME>/0.1.0' \

--data-raw '{

  "amount": 110,

  "currency": "EUR",

  "orderId": "14379133960355",

  "description": "Test Shop - #14379133960355",

  "customer": {

    "email": "email@example.com"

   },

  "callbackUrl": "https://example.com/checkout/callback"

}'
```

server.js

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



// Initialize with Partner API Key, Account ID, and User-Agent

const monei = new Monei('<PARTNER_API_KEY>', {

  accountId: '<MONEI_ACCOUNT_ID>',

  userAgent: 'MONEI/<PARTNER_NAME>/0.1.0'

});



async function createMerchantPayment() {

  try {

    const payment = await monei.payments.create({

      amount: 110,

      currency: 'EUR',

      orderId: '14379133960355',

      description: 'Test Shop - #14379133960355',

      customer: {

        email: 'email@example.com'

      },

      callbackUrl: 'https://example.com/checkout/callback'

    });

    console.log('Payment created:', payment.id);

    // Handle the payment response (e.g., redirect URL)

  } catch (error) {

    console.error('Error creating payment:', error.message);

  }

}



createMerchantPayment();
```

server.php

```
<?php

require_once 'vendor/autoload.php';



use Monei\MoneiClient;

use Monei\Model\CreatePaymentRequest;

use Monei\Model\PaymentCustomer;



// Initialize with Partner API Key

$monei = new MoneiClient('<PARTNER_API_KEY>');



// Set Account ID and User-Agent for the specific merchant

$monei->setAccountId('<MONEI_ACCOUNT_ID>');

$monei->setUserAgent('MONEI/<PARTNER_NAME>/0.1.0');



try {

  $payment = $monei->payments->create(

      new CreatePaymentRequest([

        'amount' => 110,

        'currency' => 'EUR',

        'order_id' => '14379133960355',

        'description' => 'Test Shop - #14379133960355',

        'customer' => new PaymentCustomer([

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

        ]),

        'callback_url' => 'https://example.com/checkout/callback'

      ])

  );

  echo 'Payment created: ' . $payment->getId();

  // Handle the payment response (e.g., redirect URL)



} catch (\Exception $e) {

  echo 'Error creating payment: ' . $e->getMessage();

}

?>
```

server.py

```
import Monei

from Monei import CreatePaymentRequest, PaymentCustomer



# Initialize with Partner API Key

monei = Monei.MoneiClient(api_key='<PARTNER_API_KEY>')



# Set Account ID and User-Agent for the specific merchant

monei.set_account_id('<MONEI_ACCOUNT_ID>')

monei.set_user_agent('MONEI/<PARTNER_NAME>/0.1.0')



try:

    payment = monei.payments.create(

        CreatePaymentRequest(

            amount=110,

            currency="EUR",

            order_id="14379133960355",

            description="Test Shop - #14379133960355",

            customer=PaymentCustomer(

                email="email@example.com"

            ),

            callback_url="https://example.com/checkout/callback"

        )

    )

    print(f'Payment created: {payment.id}')

    # Handle the payment response (e.g., redirect URL)

except Exception as e:

    print(f'Error creating payment: {e}')
```

### GraphQL API[​](#graphql-api "Direct link to GraphQL API")

Full reference: [GraphQL API](https://docs.monei.com/apis/graphql/.md).

POST https\://graphql.monei.com

```
curl --request POST 'https://graphql.monei.com' \

--header 'Authorization: <PARTNER_API_KEY>' \

--header 'MONEI-Account-ID: <MONEI_ACCOUNT_ID>' \

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

--header 'User-Agent: MONEI/<PARTNER_NAME>/0.1.0' \

--data-raw '{"query":"{account {name status}}"}'
```

### In-person NFC payments[​](#in-person-nfc-payments "Direct link to In-person NFC payments")

Full reference: [MONEI Pay SDK](https://docs.monei.com/monei-pay/app-integration/getting-started/.md).

Accept Tap-to-Pay card payments directly inside your own merchant app, on iOS, Android, React Native, or Web. Your backend generates a short-lived POS auth token for the merchant, then hands it to the SDK on the merchant's device.

* cURL
* Node.js
* PHP
* Python

POST https\://api.monei.com/v1/pos/auth-token

```
curl --request POST 'https://api.monei.com/v1/pos/auth-token' \

--header 'Authorization: <PARTNER_API_KEY>' \

--header 'MONEI-Account-ID: <MONEI_ACCOUNT_ID>' \

--header 'User-Agent: MONEI/<PARTNER_NAME>/0.1.0'
```

server.js

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



const monei = new Monei('<PARTNER_API_KEY>', {

  accountId: '<MONEI_ACCOUNT_ID>',

  userAgent: 'MONEI/<PARTNER_NAME>/0.1.0'

});



const {token} = await monei.posAuthToken.create();

// Send `token` to the merchant app
```

server.php

```
<?php

require_once 'vendor/autoload.php';



use Monei\MoneiClient;



$monei = new MoneiClient('<PARTNER_API_KEY>');

$monei->setAccountId('<MONEI_ACCOUNT_ID>');

$monei->setUserAgent('MONEI/<PARTNER_NAME>/0.1.0');



$result = $monei->posAuthToken->create();

$token = $result->getToken();

// Send $token to the merchant app

?>
```

server.py

```
import Monei



monei = Monei.MoneiClient(api_key='<PARTNER_API_KEY>')

monei.set_account_id('<MONEI_ACCOUNT_ID>')

monei.set_user_agent('MONEI/<PARTNER_NAME>/0.1.0')



result = monei.pos_auth_token.create()

token = result.token

# Send token to the merchant app
```

The response contains a JWT valid for 24 hours, scoped to the merchant identified by `MONEI-Account-ID`. Pass it to the SDK on the merchant's device. Optionally pass `pointOfSaleId` / `storeId` in the body to scope the token to a specific terminal or store.

warning

Refunds must never be automated. Each refund requires a written, traceable instruction from the merchant. Partners are liable for any unauthorized refund and may have their contract terminated.

## Test the integration[​](#test-the-integration "Direct link to Test the integration")

You can build the full partner integration end-to-end before a single merchant signs a contract.

1. **Register a test merchant** via your registration link: `https://dashboard.monei.com/signup&promo=<PARTNER_CODE>`. No need to complete onboarding — the test sub-account is already active.

2. **Get the test sub-account ID**: in the merchant dashboard, toggle test mode (top-nav switch) → Settings → copy the Account ID.

   ![MONEI Dashboard header with the switch set to Test, so the Dashboard shows test data](/img/dashboard/en/dashboard-header-test-mode.png)

   MONEI Dashboard header with the switch set to Test, so the Dashboard shows test data

3. **Get your test partner API key**: log in to [admin.monei.com](https://admin.monei.com) → toggle test mode → [Settings → API Access](https://admin.monei.com/settings/api) → click **Create API Key** if you don't have one yet → copy the `pk_test_*` key.

4. **Call the API** with both headers, using test card numbers and response simulators documented in [Testing](https://docs.monei.com/testing/.md):

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

--header 'Authorization: pk_test_<...>' \

--header 'MONEI-Account-ID: <TEST_SUB_ACCOUNT_ID>' \

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

--data-raw '{"amount": 110, "currency": "EUR", "orderId": "test-1"}'
```

## Common authentication failures[​](#common-authentication-failures "Direct link to Common authentication failures")

When you get a 401, check these in order:

* **Mode mismatch** — using a `pk_live_*` key with a test `MONEI-Account-ID` (or vice versa). Partner key and sub-account must be in the same mode.
* **IP not allowlisted** — if you registered an IP allowlist (optional but recommended), calls must originate from one of those IPs. To add or remove an IP, email <connect@monei.com>.
* **Wrong `MONEI-Account-ID`** — the sub-account must belong to your partner account. Sub-accounts of other partners (or standalone accounts) are rejected.
* **Missing `MONEI-Account-ID`** — without it, the key authenticates as the partner itself, which only works for a small subset of partner-only endpoints. For all merchant operations, include the header.

## Webhooks[​](#webhooks "Direct link to Webhooks")

Configure webhooks in the [Partner Dashboard](https://admin.monei.com) at **Settings → Webhooks**. Each webhook is an endpoint URL plus the subset of event types you want to receive.

Webhooks on your partner account receive a **fan-out of events from every merchant** bound to it — no per-sub-account configuration needed. The payload's `accountId` identifies the merchant.

### Subscribing to events[​](#subscribing-to-events "Direct link to Subscribing to events")

Common partner subscriptions:

| Group                        | Useful events                                                                                                                                               |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Merchant lifecycle           | `account.pending`, `account.approved`, `account.rejected`, `account.activated`, `account.suspended`, `account.blocked`, `account.unblocked`                 |
| Payouts                      | `account.iban_updated`, `account.payout_iban_updated`, `account.payouts_resumed`, `account.payouts_suspended`, `settlement.completed`, `settlement.pending` |
| Payments (per merchant)      | `charge.succeeded`, `charge.failed`, `charge.refunded`, `charge.chargeback`, `refund.succeeded`                                                             |
| Subscriptions (per merchant) | `subscription.activated`, `subscription.canceled`, `subscription.past_due`                                                                                  |
| Billing                      | `account_invoice.paid`, `account_invoice.past_due`                                                                                                          |

The full catalog is available in the dashboard's event-type picker when you add or edit a webhook.

### Payload[​](#payload "Direct link to Payload")

MONEI POSTs `application/json` with this envelope on every event:

| Field      | Type    | Description                                                                                                 |
| ---------- | ------- | ----------------------------------------------------------------------------------------------------------- |
| id         | string  | Event ID.                                                                                                   |
| type       | string  | Event type (e.g. `charge.succeeded`).                                                                       |
| objectType | string  | One of `charge`, `subscription`, `account`, `account_invoice`, `settlement`, `provider`.                    |
| objectId   | string  | ID of the affected object.                                                                                  |
| accountId  | string  | Merchant sub-account the event belongs to. Use this to route events to the right merchant in your system.   |
| livemode   | boolean | `true` for live events, `false` for test events. Match this to the API key you use to verify the signature. |
| createdAt  | number  | Event timestamp, unix seconds.                                                                              |
| object     | object  | The affected object. Shape depends on `objectType` — see tabs below.                                        |

* charge
* subscription
* account
* account\_invoice

`object` is a Payment — full field reference at [Payment schema](https://docs.monei.com/apis/rest/schemas/payment/.md).

```
{

  "id": "evt_5fda0f3b7b2f4e0d…",

  "type": "charge.succeeded",

  "objectType": "charge",

  "objectId": "ch_8e2f…",

  "accountId": "<MERCHANT_SUB_ACCOUNT_ID>",

  "livemode": true,

  "createdAt": 1748390400,

  "object": {

    "id": "ch_8e2f…",

    "amount": 1099,

    "currency": "EUR",

    "status": "SUCCEEDED",

    "orderId": "ORDER-4421",

    "customer": {"email": "buyer@example.com"},

    "paymentMethod": {"method": "card", "card": {"brand": "VISA", "last4": "4242"}}

  }

}
```

`object` is a Subscription — full field reference at [Subscription schema](https://docs.monei.com/apis/rest/schemas/subscription/.md).

```
{

  "id": "evt_…",

  "type": "subscription.activated",

  "objectType": "subscription",

  "objectId": "sub_…",

  "accountId": "<MERCHANT_SUB_ACCOUNT_ID>",

  "livemode": true,

  "createdAt": 1748390400,

  "object": {

    "id": "sub_…",

    "amount": 999,

    "currency": "EUR",

    "interval": "month",

    "intervalCount": 1,

    "status": "ACTIVE",

    "nextPaymentAt": 1751068800,

    "customer": {"email": "buyer@example.com"}

  }

}
```

`object` is the affected merchant account. Fired by every `account.*` and `master_account.*` event type.

| Field                                       | Type    | Description                                                                                                                         |
| ------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| id                                          | string  | Sub-account ID.                                                                                                                     |
| name                                        | string  | Trading name.                                                                                                                       |
| status                                      | string  | `PENDING_CONTRACT`, `PENDING_APPROVAL`, `APPROVED`, `ACTIVE`, `REJECTED`, `SUSPENDED`, `BLOCKED`.                                   |
| statusReason                                | string  | Machine-readable reason for the current status (when applicable).                                                                   |
| statusNote                                  | string  | Free-text note from MONEI Ops (when applicable).                                                                                    |
| test                                        | boolean | `true` for the test twin sub-account.                                                                                               |
| adminEmail                                  | string  | Merchant admin email.                                                                                                               |
| iban                                        | string  | Payout IBAN, obfuscated (first 4 + last 4 only).                                                                                    |
| externalId                                  | string  | Partner-supplied merchant identifier (if you use the External ID flow).                                                             |
| language                                    | string  | Dashboard language code.                                                                                                            |
| billingPlan                                 | string  | Internal billing plan. `MONEI_ONE` on new accounts; the legacy `MONEI_PLUS`, `MONEI_POS`, `MONEI_X` on older ones (see note below). |
| blocked                                     | boolean | `true` if MONEI has blocked the account.                                                                                            |
| churned                                     | boolean | `true` if the merchant has churned.                                                                                                 |
| churnReason / churnReasonOption / churnNote | string  | Churn metadata (when `churned: true`).                                                                                              |
| contactPerson                               | object  | `{name, email, phone}` of the merchant admin.                                                                                       |
| business                                    | object  | `{legalName, legalForm, email, phone, website, country, address, documentNumber, status, servicesDescription}`.                     |

info

`billingPlan` is an internal billing attribute, not part of the payments contract. [MONEI One](https://docs.monei.com/manage-account/monei-fees/.md) is the single plan for every new merchant, so new sub-accounts report `MONEI_ONE`. Merchants onboarded before its launch keep their original plan and still report `MONEI_PLUS`, `MONEI_POS` or `MONEI_X`. Do not branch your integration on this field.

```
{

  "id": "evt_…",

  "type": "account.approved",

  "objectType": "account",

  "objectId": "<MERCHANT_SUB_ACCOUNT_ID>",

  "accountId": "<MERCHANT_SUB_ACCOUNT_ID>",

  "livemode": true,

  "createdAt": 1748390400,

  "object": {

    "id": "<MERCHANT_SUB_ACCOUNT_ID>",

    "name": "ACME Shop",

    "status": "APPROVED",

    "test": false,

    "adminEmail": "owner@acme.com",

    "iban": "ES12********6789",

    "externalId": "merchant-7421",

    "language": "es",

    "billingPlan": "MONEI_ONE",

    "blocked": false,

    "business": {

      "legalName": "ACME COMERCIO SL",

      "documentNumber": "B12345678",

      "country": "ES",

      "website": "https://acme.com",

      "email": "info@acme.com",

      "status": "active"

    }

  }

}
```

`object` is the merchant's MONEI invoice for accrued platform fees. Fired by `account_invoice.*` events.

| Field       | Type   | Description                                                                                         |
| ----------- | ------ | --------------------------------------------------------------------------------------------------- |
| id          | string | Invoice ID.                                                                                         |
| number      | string | Human-readable invoice number printed on the PDF.                                                   |
| accountId   | string | Sub-account the invoice belongs to.                                                                 |
| status      | string | `PENDING`, `PAID`, `PAST_DUE`, `UNPAID`.                                                            |
| period      | object | `{from, to}` unix seconds covering the billing period.                                              |
| currency    | string | ISO 4217 code.                                                                                      |
| amount      | number | Net amount in minor units.                                                                          |
| tax         | number | Tax in minor units.                                                                                 |
| totalAmount | number | `amount + tax`, minor units.                                                                        |
| lineItems   | array  | Per-charge line items: `{name, quantity, unitType (money\|percent), unitAmount, amount, currency}`. |
| business    | object | `{legalName, documentNumber, iban (obfuscated), mandateSignatureTimestamp}`.                        |
| createdAt   | number | Invoice creation timestamp, unix seconds.                                                           |

```
{

  "id": "evt_…",

  "type": "account_invoice.paid",

  "objectType": "account_invoice",

  "objectId": "inv_…",

  "accountId": "<MERCHANT_SUB_ACCOUNT_ID>",

  "livemode": true,

  "createdAt": 1748390400,

  "object": {

    "id": "inv_…",

    "number": "1/2026-14510",

    "accountId": "<MERCHANT_SUB_ACCOUNT_ID>",

    "status": "PAID",

    "period": {"from": 1773014400, "to": 1773619199},

    "currency": "EUR",

    "amount": 153,

    "tax": 0,

    "totalAmount": 153,

    "lineItems": [

      {

        "name": "Flat fee per Credit card transaction",

        "quantity": 1,

        "unitType": "money",

        "unitAmount": 24,

        "amount": 24,

        "currency": "EUR"

      },

      {

        "name": "MONEI daily platform usage fee",

        "quantity": 7,

        "unitType": "money",

        "unitAmount": 10,

        "amount": 70,

        "currency": "EUR"

      }

    ],

    "business": {

      "legalName": "ACME COMERCIO SL",

      "documentNumber": "B12345678",

      "iban": "ES93********0012"

    }

  }

}
```

### Verifying the signature[​](#verifying-the-signature "Direct link to Verifying the signature")

Every request carries a `MONEI-Signature` header in the form `t=<unix-seconds>,v1=<hex>`. Compute `HMAC-SHA256("<t>.<rawBody>", <PARTNER_API_KEY>)` and compare to `v1`. Use the same Partner API Key (live or test) that matches the event's `livemode`. Full walkthrough: [Verify signature](https://docs.monei.com/guides/verify-signature/.md).

### Delivery and retries[​](#delivery-and-retries "Direct link to Delivery and retries")

* Reply with `2xx` to acknowledge. Anything else (including `3xx`) is treated as a failure.
* MONEI retries failed deliveries with backoff for several days, then gives up.
* `User-Agent: MONEI/1.0` identifies our requests.

note

The legacy `accountCallbackUrl` configured during partner onboarding still works — it fires only on `account.pending`, `account.approved`, and `account.rejected` with a limited payload — but it is being phased out. New integrations should use the webhook configuration in the Partner Dashboard.

## Manage your partner account[​](#manage-your-partner-account "Direct link to Manage your partner account")

All partner-account management is self-service in the [Partner Dashboard](https://admin.monei.com).

### Authorized users[​](#authorized-users "Direct link to Authorized users")

Invite additional team members at [admin.monei.com/settings/users](https://admin.monei.com/settings/users) → **Add new user**. Provide their email, name, and phone number — they'll receive an invitation to set their password. All partner users are MFA-enforced.

### Security[​](#security "Direct link to Security")

[admin.monei.com/settings/security](https://admin.monei.com/settings/security) — three independent controls:

* **Allowed IPs** — every API call against your Partner API Key must originate from one of these IPs. Acts as a second line of defense if a key leaks: a stolen key from outside your infrastructure becomes unusable.
* **Allowed domains** — restricts which domains can load the Hosted Payment Page for merchants bound to your account. Prevents your merchants' checkout from being embedded by unrelated sites.
* **Require external ID** — when enabled, every merchant signing up via your registration link must carry a valid `mid` + `h` HMAC (see [Registration link](#registration-link)). Use this if you only want sign-ups that originate from your own onboarding flow, never direct visits to the link.

### Branding[​](#branding "Direct link to Branding")

[admin.monei.com/settings/branding](https://admin.monei.com/settings/branding) — upload your logo. On the merchant sign-up page opened from your registration link, your logo appears **alongside** MONEI's (separated by a `+`), signaling the co-branded partnership. Use this so merchants recognize your platform as the source of the payments offering instead of arriving at an unbranded MONEI sign-up.

### Email notifications[​](#email-notifications "Direct link to Email notifications")

[admin.monei.com/settings/emails](https://admin.monei.com/settings/emails) — add extra recipient lists for operational emails MONEI sends about your partner account:

* **Webhook emails** — recipients alerted when webhook delivery to your endpoint starts failing. Route to your ops or on-call mailbox so you fix a broken endpoint before piling up missed events.
* **Churned emails** — recipients alerted when a merchant bound to you churns. Route to your account-management or finance team so they can follow up without polling the API.
