Integrate PayParts (Quickstart)

Add PayParts to any webstore checkout. This guide is self-contained: it gives you everything needed to get a working integration end-to-end — a backend call to create a session, then a frontend that renders the payment components. It is framework-agnostic, so it maps to any stack (Node, Medusa.js, Shopify backend, etc.). For the deep detail on any step, follow the links at the bottom of each section.

How it works (30 seconds)

  1. Your backend calls POST /v1/session/start with the order amount and gets back a sessionToken.
  2. Your frontend loads the PayParts SDK, initializes it with that sessionToken, and mounts the payment components (card form, wallets, method list, etc.).
  3. Payment completes — the consumer finishes on the payment page, you confirm via the returnUrl / onSuccess event, and your backend is notified via the exchangeUrl webhook.
Consumer ──▶ Your Checkout (PayParts components)
                │
                ├─ POST /v1/session/start ──▶ PayParts backend  ──▶ sessionToken
                │
                └─ SDK init(sessionToken) ──▶ render + complete payment ✦
                                                   │
                            returnUrl / onSuccess ◀─┘
                            exchangeUrl webhook ──▶ your backend

1. Environments

PayParts has two environments. Use the matching one on backend and frontend.

ProductionZero (pre-production)
API base URLhttps://parts.pay.nlhttps://zero-parts.pay.nl
SDK scripthttps://parts.pay.nl/sdk/payparts-sdk.jshttps://zero-parts.pay.nl/sdk/payparts-sdk.js
⚠️

Zero can contain features not yet on Production (e.g. session updates). Verify each feature is available in the environment you target.


2. Backend: create a session

Your backend creates the session before the checkout page loads. This is a simple authenticated REST call.

⚠️

PayParts ≠ the classic Pay Payment API. PayParts sessions use POST /v1/session/start (and /v1/session/{id}/pay). This is not Pay's legacy card API (/v1/Payment/authenticate, /authorize, /getAuthenticationStatus). If an AI/agent tool surfaces endpoints like /v1/Payment/*, ignore them — those belong to the older hosted/classic Pay products, not PayParts. Only /v1/session/* is used for PayParts.

Authentication

POST /v1/session/start uses HTTP Basic auth with your Sales Location credentials:

Authorization: Basic <base64(sl-code:secret)>

Minimal request

curl -X POST 'https://parts.pay.nl/v1/session/start' \
  -H 'Authorization: Basic <base64(sl-code:secret)>' \
  -H 'Content-Type: application/json' \
  -d '{
    "returnUrl": "https://example.com/return",
    "exchangeUrl": "https://example.com/exchange",
    "amount": {
      "value": 1,
      "currency": "EUR"
    }
  }'

Successful response

{
  "sessionId": "a3fc44c532784ce1b8ac2f2a003e1239",
  "sessionToken": "st_..."
}

Take the sessionToken — you pass it to the SDK on the frontend. Never expose your Sales Location secret to the browser; keep this call on the backend.

💡

Amounts are in cents. amount.value (and every products[].price.value) is the smallest currency unit — €0.01 = 1 (one cent). value must be ≥ 1. Never send decimal euros.

📘

Order-based / redirect methods (e.g. iDEAL) need more. The minimal call above only covers methods paid inline. When the payer can pick a redirect method such as iDEAL, the session must also contain the full order (line items with vatPercentage, plus delivery and invoice addresses) and the customer — otherwise POST /v1/session/{id}/pay fails with 422 PAY-2880 requiredDataMissing. See Create a session from your backend for the full request.


3. Frontend: add the SDK

On your checkout page, load the SDK, initialize it with the sessionToken, and mount the components you want.

Load the SDK

<script type="module" src="https://parts.pay.nl/sdk/payparts-sdk.js"></script>

Initialize and mount components

const checkout = await window.PayPartsSDK.init({
  sessionToken: '<sessionToken from your backend>', // never store this in source
  apiUrl: 'https://parts.pay.nl',                   // match your environment
  country: 'NL',
  language: 'en',
});

// Mount the components where you want them on the page
const card = checkout.prepare('card-payments');        // card form
await card.bind('#payparts-card');

const express = checkout.prepare('checkout-buttons');  // Apple Pay / PayPal / Google Pay
await express.bind('#payparts-express');

const methods = checkout.prepare('methods');           // full payment method list
await methods.bind('#payparts-methods');

The component names are lowercase strings: 'card-payments', 'checkout-buttons', 'methods', 'giftcard', 'applepay', 'paypal', 'googlepay', 'click-to-pay-methods', 'user-optimized'.


4. Finish the payment

How the flow completes depends on the method:

  • Redirect methods (e.g. iDEAL): the payer is sent to a payment page. Return them to your returnUrl when done, where you confirm the outcome.
  • Inline methods (cards, wallets): confirm via the SDK onSuccess event.
  • Your backend is always notified by the webhook sent to your exchangeUrl with the payment result.

A minimal event handler:

checkout.events = {
  onSuccess: (event) => {
    // payment succeeded — show confirmation / complete the order
    console.log('Order:', event.orderId);
    event.resolve();
  },
  onError: (event) => {
    console.error('Payment failed:', event.error, event.errors);
    event.resolve(); // allow retry
  },
};

See Handle events for the full event model.


5. Gotchas that break integrations

These are the most common reasons a first integration fails. Handle them up front:

SymptomCause / fix
Money is 100× offAmounts are in cents, not euros. €0.01 = 1 (one cent); minimum is 1.
422 PAY-2880 requiredDataMissing on paySession created without full order + customer. Add them (required for iDEAL etc.).
422 PAY-1422 reference: Deze waarde is niet geldig.reference contains invalid chars (commonly _, e.g. a cart_... id). Send a plain order number.
410 GoneSession expired. Set a longer expire or create a fresh session.
Component not appearingCheck the browser console / that the target element exists before bind().
📘

Webhook signature: PAY.Parts exchanges are signed using the Service Location secret. Verify the HMAC with the secret belonging to the SL-XXXX-XXXX in signature-keyid — not your merchant API token.


Next steps


Did this page help you?