Skip to content

JavaScript SDK

The SDK creates a session via the v1 API and opens checkout in an iframe on plurelpay.com. Card entry happens there (hosted checkout). Your page stays visible behind a modal overlay. All browser paths share the same credentials: publishable key + signing secret — not the secret API key.

Copy this page as a setup prompt for your coding assistant.

Sandbox demo store

Walk through group checkout end to end at the demo store. The store runs in sandbox mode with test cards — you can split an order, see how settlement looks, and confirm webhook delivery. No real charges.

Need a reference storefront for your engineers? Source is public on GitHub: plurel-company/split-shop.

Choose an integration path

PathBest forInstallCredentials
CDN script tagWordPress, static HTML, no bundler<script src="https://plurelpay.com/sdk/v1/1.2.1/plurel.js">Publishable key + signing route
npm — vanillaVite, webpack, bundled JS without Reactnpm install @plurel/sdkSame as CDN
npm — ReactNext.js, Remix, React SPAsnpm install @plurel/react-sdkSame as CDN (react-sdk wraps core)
REST API onlyCustom checkout UI, no modal embedcurl or @plurel/sdk PlurelApiClientSecret API key (+ headless approval)

Prerequisite: cart signing

Every browser path above uses a publishable key. You must deploy POST /api/cart/sign on your domain and set PLUREL_SIGNING_SECRET. Full spec: Cart signing. Credential cheat sheet: Credentials.

Use @plurel/sdk and @plurel/react-sdk at version 1.2.1 or later for the three-figure checkout button and byte-compatible HMAC signing with the Plurel Pay API.

Why two npm packages?

@plurel/sdk is the core library: hosted modal, session client, TypeScript types, cart validation, and the server-only @plurel/sdk/signing subpath. @plurel/react-sdk is a thin React layer on top — PlurelProvider, PlurelButton, and polling hooks. It depends on @plurel/sdk and adds no payment logic of its own.

PackageInstall whenMain exports
@plurel/sdkAny integration (required for signing on the server too)Plurel, PlurelApiClient, PlurelSessionClient, types; signing via @plurel/sdk/signing
@plurel/react-sdkReact / Next.js apps onlyPlurelProvider, PlurelButton, usePlurelSession, useGroupStatus

Prefer the version-pinned CDN URL (https://plurelpay.com/sdk/v1/1.2.1/plurel.js) so browser caches pick up CTA updates. Floating /sdk/v1/plurel.js still works but may lag behind if an older response was cached as immutable. Load plurel.js, which sets window.Plurel.

1. CDN script tag (vanilla HTML)

No build step. Loads window.Plurel globally. Best for quick pilots and legacy stacks.

index.html
<script src="https://plurelpay.com/sdk/v1/1.2.1/plurel.js" async></script>
<script>
  const cart = {
    total: 12800,
    currency: "usd",
    items: [{ id: "sku_1", name: "Ticket", quantity: 2, unit_price: 5000 }],
    tax: 1000,
    shipping: 1000,
    fees: [
      { id: "cleaning", label: "Cleaning fee", amount: 500 },
      { id: "processing", label: "Ticket processing", amount: 300 },
    ],
    metadata: { order_ref: "ORD-1042" },
  };

  // From your logged-in shopper when available:
  const customer = { name: "Alex", email: "alex@example.com" };

  Plurel.init({
    merchantId: "plurel_merch_YOUR_ID",
    publishableKey: "plurel_pk_test_YOUR_KEY",
    getSignature: async (c) => {
      const res = await fetch("/api/cart/sign", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ cart: c }),
      });
      if (!res.ok) throw new Error("Cart signing failed");
      return (await res.json()).signature;
    },
  });

  Plurel.createButton({
    cart,
    group: { minSize: 2, maxSize: 6 },
    customer,
    success_url: "https://your-store.com/checkout/success",
    cancel_url: "https://your-store.com/checkout",
    callbacks: {
      onGroupFunded: (id, ref) => console.log("Funded", id, ref),
    },
  }).mount("#plurel-button");
</script>

2. npm — bundled JavaScript (no React)

Import Plurel from @plurel/sdk when you have a bundler but are not using React. API is identical to the CDN global.

checkout.ts
import { Plurel } from "@plurel/sdk";

Plurel.init({
  merchantId: import.meta.env.VITE_PLUREL_MERCHANT_ID,
  publishableKey: import.meta.env.VITE_PLUREL_PUBLISHABLE_KEY,
  getSignature: async (cart) => {
    const res = await fetch("/api/cart/sign", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ cart }),
    });
    if (!res.ok) throw new Error("Cart signing failed");
    return (await res.json()).signature;
  },
});

Plurel.createButton({ cart, group: { minSize: 2, maxSize: 6 } }).mount("#plurel-button");

Server-side signing uses a separate import — never bundle this in client code:

app/api/cart/sign/route.ts
import { createCartSignature } from "@plurel/sdk/signing";

export async function POST(req: Request) {
  const { cart } = await req.json();
  const signature = createCartSignature(cart, process.env.PLUREL_SIGNING_SECRET!);
  return Response.json({ signature });
}

3. npm — React

Install @plurel/react-sdk (pulls in @plurel/sdk automatically). Wrap checkout in PlurelProvider; mount PlurelButton where the shopper pays.

PlurelProvider + PlurelButton
"use client";

import { PlurelProvider, PlurelButton } from "@plurel/react-sdk";

const cart = {
  total: 12000,
  currency: "usd",
  items: [{ id: "sku_1", name: "Ticket", quantity: 2, unit_price: 5000 }],
  metadata: { order_ref: "ORD-1042" },
};

export default function Checkout() {
  const customer = { name: "Alex", email: "alex@example.com" };

  return (
    <PlurelProvider
      merchantId={process.env.NEXT_PUBLIC_PLUREL_MERCHANT_ID!}
      publishableKey={process.env.NEXT_PUBLIC_PLUREL_PUBLISHABLE_KEY!}
      environment="sandbox"
      getSignature={async (c) => {
        const res = await fetch("/api/cart/sign", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ cart: c }),
        });
        if (!res.ok) throw new Error("Cart signing failed");
        return (await res.json()).signature;
      }}
    >
      <PlurelButton
        cart={cart}
        group={{ minSize: 2, maxSize: 6 }}
        customer={customer}
        success_url="https://your-store.com/checkout/success"
        cancel_url="https://your-store.com/checkout"
        appearance={{ size: "md", variant: "primary" }}
      />
    </PlurelProvider>
  );
}

Hooks for status outside the modal: usePlurelSession(sessionId), useGroupStatus(sessionId) (polls GET /api/v1/sessions/:id every 3s).

Prefer a working storefront: the sandbox demo store (sandbox checkout) · source on GitHub.

4. REST API only (no SDK button)

Skip the modal embed entirely. Your server creates sessions with a secret API key (plurel_sk_*). Requires headless_api_enabled on your merchant to omit cart signatures. See REST API and Credentials.

Node — PlurelApiClient
import { PlurelApiClient } from "@plurel/sdk";

const client = new PlurelApiClient({
  apiKey: process.env.PLUREL_SECRET_KEY!,
  merchantId: "plurel_merch_YOUR_ID",
});

const session = await client.createSession({
  cart,
  group_config: { min_size: 2, max_size: 6 },
});

Plurel.init(options)

Call once per page (CDN / npm vanilla) or via PlurelProvider (React) before opening checkout.

OptionRequiredNotes
merchantIdYesplurel_merch_* from dashboard
publishableKeyYesplurel_pk_test_* or plurel_pk_live_*
getSignatureYes*async (cart) => hex string from your /api/cart/sign
environmentNosandbox | production — keys control sandbox vs live mode
themeNoauto | light | dark
apiBaseUrlNoDefault https://plurelpay.com/api/v1
payBaseUrlNoDefault https://plurelpay.com (iframe origin)

*Omit if every button passes signature directly.

Button appearance

Layout follows the Apple Pay button pattern: flat fill, rounded rectangle, 44px default height, three-figure mark + wordmark centered. The label is always Split with Plurel. Merchants may adjust layout and variant only — do not recolor or recompose the lockup (see terms).

appearance fieldValuesNotes
fullWidthboolean (default true)Span container width
sizesm | md | lgHeight and typography scale
variantprimary | outline | darkprimary = terra (default) · dark = black, pairs with Apple Pay · outline = white + hairline

Static SVG pack and dynamic endpoint for theme editors: /brand/buttons/ and /api/button?variant=primary&size=md&logo=1 — also in the merchant integration wizard.

Plurel.createButton(options)

Recommended session fields

Pass these on every createSession or Plurel.createButton call when your storefront already knows the values. The API accepts sessions without them, but production integrations are smoother when you send organizer context up front.

FieldPass whenWhy
customer.nameYou know the shopper starting the groupPre-fills the organizer in group setup; avoids blank “Person 1” labels.
customer.emailYou have a logged-in or checkout emailSupport and correlation; required for POST /sessions/:id/remind to reach pending members.
customer.phoneYou already collect itOptional support contact — only send if you use it elsewhere.
success_urlHosted modal / SDK embedRedirects the organizer back to your store after the group is fully funded.
cancel_urlHosted modal / SDK embedRedirects the organizer after they close the modal or the group is cancelled (refunds run automatically).
cart.metadata.order_refAlways (your order or cart id)Echoes as order_ref on webhooks and GET /sessions — tie fulfillment to your system.

Guest payers (share links)

Do not collect payer email or address in your storefront before Plurel Pay opens. Friends who open /s/{shareId} pay through hosted checkout; Plurel Pay stores payer email from checkout when provided (receipt / billing details). Your integration only needs to pass organizer customer at session create.

Privacy

If you pass customer.email or customer.phone, disclose that in your privacy policy and only send fields you already collect for checkout. See Go live and your obligations under Plurel Pay's privacy policy for participant data.

OptionNotes
carttotal, currency, items; optional tax, shipping, fees[], metadata (use metadata.order_ref)
groupminSize, maxSize, defaultMode, allowedModes, expiryHours
signaturePre-computed HMAC; skips getSignature for this button
getSignatureOverrides init-level signer
customerOrganizer only — name, email, phone when your storefront knows them
success_urlHTTPS URL — redirect organizer after group funded (recommended in embed)
cancel_urlHTTPS URL — redirect organizer after cancel/abandon (recommended in embed)
callbacksonGroupCreated, onGroupFunded, onGroupExpired, onGroupCancelled, onError
appearancefullWidth, size (sm | md | lg), variant (primary | outline | dark) — label is always Split with Plurel

Instance methods: mount(selector | element), unmount(), open(), destroy().

Split modes

ModeMeaning
equalEven split of cart total
itemizedEach member takes line items
customOrganizer sets amounts
percentagePercentage split
host-paysOrganizer absorbs remainder

Modal URL and postMessage

After session create, the SDK opens {payBaseUrl}/session/{id}?access={token}&embed=1&parent_origin={your origin}. Callbacks fire from postMessage; the SDK also polls GET /api/v1/sessions/:id every 3 seconds as a fallback. Fulfill orders on group.funded webhooks, not callbacks alone — see Webhooks.

Fees on create

POST /sessions returns merchant_fee_rate (default 0.02) and consumer_fee_rate (0.02 per share, plus a flat $0.30 per payment). The modal shows these to payers.