JavaScript SDK
The SDK creates a session via the v1 API and opens checkout in an iframe on splitante.com. Card entry happens there (Stripe). 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.
Full implementation example
Try the live sandbox store at ante-demo-store.vercel.app — a complete Next.js shop with cart signing, @splitante/react-sdk, webhooks, and test/live credential switching.
Clone the source on GitHub: plurel-company/ante-demo-store. The repository is public (anyone can clone or fork); only Plurel maintainers can push to main.
Choose an integration path
| Path | Best for | Install | Credentials |
|---|---|---|---|
| CDN script tag | WordPress, static HTML, no bundler | <script src="https://splitante.com/sdk/v1/ante.js"> | Publishable key + signing route |
| npm — vanilla | Vite, webpack, bundled JS without React | npm install @splitante/sdk | Same as CDN |
| npm — React | Next.js, Remix, React SPAs | npm install @splitante/react-sdk | Same as CDN (react-sdk wraps core) |
| REST API only | Custom checkout UI, no modal embed | curl or @splitante/sdk AnteApiClient | Secret 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 ANTE_SIGNING_SECRET. Full spec: Cart signing. Credential cheat sheet: Credentials.
Use @splitante/sdk and @splitante/react-sdk at version 0.1.7 or later for cart.fees and byte-compatible HMAC signing with the Ante API.
Why two npm packages?
@splitante/sdk is the core library: hosted modal, session client, TypeScript types, cart validation, and the server-only @splitante/sdk/signing subpath. @splitante/react-sdk is a thin React layer on top — AnteProvider, AnteButton, and polling hooks. It depends on @splitante/sdk and adds no payment logic of its own.
| Package | Install when | Main exports |
|---|---|---|
| @splitante/sdk | Any integration (required for signing on the server too) | Ante, AnteApiClient, AnteSessionClient, types; signing via @splitante/sdk/signing |
| @splitante/react-sdk | React / Next.js apps only | AnteProvider, AnteButton, useAnteSession, useGroupStatus |
The CDN bundle at /sdk/v1/ante.js is the same core compiled to window.Ante — use CDN if you do not have a JavaScript bundler. You do not need both npm packages unless you use React; non-React sites use CDN or @splitante/sdk alone.
1. CDN script tag (vanilla HTML)
No build step. Loads window.Ante globally. Best for quick pilots and legacy stacks.
<script src="https://splitante.com/sdk/v1/ante.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" };
Ante.init({
merchantId: "ante_merch_YOUR_ID",
publishableKey: "ante_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;
},
});
Ante.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("#ante-button");
</script>2. npm — bundled JavaScript (no React)
Import Ante from @splitante/sdk when you have a bundler but are not using React. API is identical to the CDN global.
import { Ante } from "@splitante/sdk";
Ante.init({
merchantId: import.meta.env.VITE_ANTE_MERCHANT_ID,
publishableKey: import.meta.env.VITE_ANTE_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;
},
});
Ante.createButton({ cart, group: { minSize: 2, maxSize: 6 } }).mount("#ante-button");Server-side signing uses a separate import — never bundle this in client code:
import { createCartSignature } from "@splitante/sdk/signing";
export async function POST(req: Request) {
const { cart } = await req.json();
const signature = createCartSignature(cart, process.env.ANTE_SIGNING_SECRET!);
return Response.json({ signature });
}3. npm — React
Install @splitante/react-sdk (pulls in @splitante/sdk automatically). Wrap checkout in AnteProvider; mount AnteButton where the shopper pays.
"use client";
import { AnteProvider, AnteButton } from "@splitante/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 (
<AnteProvider
merchantId={process.env.NEXT_PUBLIC_ANTE_MERCHANT_ID!}
publishableKey={process.env.NEXT_PUBLIC_ANTE_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;
}}
>
<AnteButton
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" }}
/>
</AnteProvider>
);
}Hooks for status outside the modal: useAnteSession(sessionId), useGroupStatus(sessionId) (polls GET /api/v1/sessions/:id every 3s).
Full implementation example: ante-demo-store.vercel.app (live sandbox) · clone github.com/plurel-company/ante-demo-store — public read/clone; Plurel maintainers only push to main.
4. REST API only (no SDK button)
Skip the modal embed entirely. Your server creates sessions with a secret API key (ante_sk_*). Requires headless_api_enabled on your merchant to omit cart signatures. See REST API and Credentials.
import { AnteApiClient } from "@splitante/sdk";
const client = new AnteApiClient({
apiKey: process.env.ANTE_SECRET_KEY!,
merchantId: "ante_merch_YOUR_ID",
});
const session = await client.createSession({
cart,
group_config: { min_size: 2, max_size: 6 },
});Ante.init(options)
Call once per page (CDN / npm vanilla) or via AnteProvider (React) before opening checkout.
| Option | Required | Notes |
|---|---|---|
| merchantId | Yes | ante_merch_* from dashboard |
| publishableKey | Yes | ante_pk_test_* or ante_pk_live_* |
| getSignature | Yes* | async (cart) => hex string from your /api/cart/sign |
| environment | No | sandbox | production — keys control sandbox vs live mode |
| theme | No | auto | light | dark |
| apiBaseUrl | No | Default https://splitante.com/api/v1 |
| payBaseUrl | No | Default https://splitante.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, mark + wordmark centered. The label is always Split with Ante. Merchants may adjust layout and variant only — do not recolor or recompose the lockup (see terms).
| appearance field | Values | Notes |
|---|---|---|
| fullWidth | boolean (default true) | Span container width |
| size | sm | md | lg | Height and typography scale |
| variant | primary | outline | dark | primary = 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.
Ante.createButton(options)
Recommended session fields
Pass these on every createSession or Ante.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.
| Field | Pass when | Why |
|---|---|---|
customer.name | You know the shopper starting the group | Pre-fills the organizer in group setup; avoids blank “Person 1” labels. |
customer.email | You have a logged-in or checkout email | Support and correlation; required for POST /sessions/:id/remind to reach pending members. |
customer.phone | You already collect it | Optional support contact — only send if you use it elsewhere. |
success_url | Hosted modal / SDK embed | Redirects the organizer back to your store after the group is fully funded. |
cancel_url | Hosted modal / SDK embed | Redirects the organizer after they close the modal or the group is cancelled (refunds run automatically). |
cart.metadata.order_ref | Always (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 Ante opens. Friends who open /s/{shareId} pay through Stripe; Ante stores payer email from Stripe 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 Ante's privacy policy for participant data.
| Option | Notes |
|---|---|
| cart | total, currency, items; optional tax, shipping, fees[], metadata (use metadata.order_ref) |
| group | minSize, maxSize, defaultMode, allowedModes, expiryHours |
| signature | Pre-computed HMAC; skips getSignature for this button |
| getSignature | Overrides init-level signer |
| customer | Organizer only — name, email, phone when your storefront knows them |
| success_url | HTTPS URL — redirect organizer after group funded (recommended in embed) |
| cancel_url | HTTPS URL — redirect organizer after cancel/abandon (recommended in embed) |
| callbacks | onGroupCreated, onGroupFunded, onGroupExpired, onGroupCancelled, onError |
| appearance | fullWidth, size (sm | md | lg), variant (primary | outline | dark) — label is always Split with Ante |
Instance methods: mount(selector | element), unmount(), open(), destroy().
Split modes
| Mode | Meaning |
|---|---|
| equal | Even split of cart total |
| itemized | Each member takes line items |
| custom | Organizer sets amounts |
| percentage | Percentage split |
| host-pays | Organizer 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.0201) and consumer_fee_rate (0.01 per share). The modal shows these to payers.