buymeamalt.Register an application ↗

BMM CONNECT DOCUMENTATION

SDK & examples

Use the official package with your existing server and user sessions.

Official SDK

The published buymeamalt package provides a server-only core API and a Next.js adapter. Requires Node.js 22+; the optional Next adapter requires Next.js 16+. Other languages can use the hosted HTTP API.

sh
npm install buymeamalt
Entry pointUse it for
buymeamaltCore protocol on your Node.js server
buymeamalt/nextNext.js route handlers and callback cookies

View the package on npm. The runnable starter uses version 0.1.0.

Next.js integration

Create a server module. Replace the auth import with your platform's existing server authentication function; it is not provided by BMM. Use your stable internal user ID, never an ID from a form or query string.

js
// lib/bmm.js
import { createBmmConnect } from "buymeamalt/next";
import { auth } from "@/auth";

export const bmm = createBmmConnect({
  origin: process.env.BMM_ORIGIN,
  clientId: process.env.BMM_CLIENT_ID,
  apiKey: process.env.BMM_API_KEY,
  callbackUrl: process.env.BMM_REDIRECT_URI,
  getUserId: async () => (await auth())?.user.id,
});

Export the handlers from separate App Router route files. The callback must match an approved URL exactly.

js
// app/api/bmm/connect/route.js
import { bmm } from "@/lib/bmm";
export const runtime = "nodejs";
export const POST = bmm.handlers.connect;
js
// app/api/bmm/callback/route.js
import { bmm } from "@/lib/bmm";
export const runtime = "nodejs";
export const GET = bmm.handlers.callback;
js
// app/api/bmm/disconnect/route.js
import { bmm } from "@/lib/bmm";
export const runtime = "nodejs";
export const POST = bmm.handlers.disconnect;

Start and disconnect with POST forms. The adapter checks the request origin, saves the signed pending attempt in an HttpOnly cookie and validates callback state, issuer, user and PKCE. It returns to your app's root with a status query for feedback. Handle connected, disconnected, denied, state, issuer, session, origin, rate_limit and unavailable. Never treat the query string alone as proof of a connection; read the connection on the server.

jsx
<form action="/api/bmm/connect" method="post">
  <button type="submit">Connect BMM</button>
</form>

Profile and support button

Read the profile on the server after checking your own user is signed in. getConnection() returns null when no active connection exists; a missing partner session throws a BmmError with status 401.

jsx
// Inside an authenticated server component
const creator = await bmm.getConnection();

if (!creator) {
  return <form action="/api/bmm/connect" method="post">
    <button type="submit">Connect BMM</button>
  </form>;
}
return <section>
  <h2>{creator.display_name || creator.username}</h2>
  {creator.support_url && (
    <a href={creator.support_url} target="_blank" rel="noopener noreferrer">
      Buy me a malt ↗
    </a>
  )}
</section>;

The attributed link is already complete. Do not rebuild its parameters. It can be null when the application's environment differs from the payment deployment. No intermediate support page is needed.

Earnings and disconnect

Fetch earnings only with consent, and tolerate consent changing between profile and earnings requests. Errors other than permission denial should produce an appropriate retry state, not fabricated zero earnings.

js
const creator = await bmm.getConnection();
let support = null;
if (creator?.earnings_shared) {
  try {
    support = await bmm.getEarnings();
  } catch (error) {
    if (error.status !== 403) throw error;
  }
}
// null means hide the earnings section, not zero earnings.
const formatted = support?.map((entry) => ({
  currency: entry.currency,
  amount: new Intl.NumberFormat(
    entry.currency === "NGN" ? "en-NG" : "en-US",
    { style: "currency", currency: entry.currency, currencyDisplay: "narrowSymbol" }
  ).format(entry.settled_earnings_minor / 100),
  count: entry.verified_payment_count,
}));

Label these Settled earnings from this application. Amounts are integer minor units after fees and refund adjustments. Never combine NGN and USD totals or call this a withdrawal balance.

After a user confirms disconnect, submit a POST to your disconnect handler. Only show success after the server confirms it. Creators can also revoke in BMM → Share → Connected apps.

jsx
<form action="/api/bmm/disconnect" method="post">
  <button type="submit">Disconnect BMM</button>
</form>

Core SDK for other Node.js frameworks

js
import { createBmmConnect } from "buymeamalt";

Configure it with the same options. begin() returns { url, cookie }; complete(callbackUrl, cookie) completes the handshake. getConnection(), getEarnings() and disconnect() work as above. These calls are server-side and use the current authenticated user from getUserId.

Your framework must set the pending cookie HttpOnly, SameSite=Lax and Secure on HTTPS; bind it to the callback path; clear it on callback; enforce same-origin connection/disconnection requests; and retain the same authenticated user. Do not put shared per-user state in a global variable or accept user IDs from browsers. See the connection protocol and security guide.

Runnable starter and AI assistant

Prefer the SDK above for supported apps. Choose a method in the quickstart to download or clone the working Next.js starter, or copy an integration prompt for your coding assistant. The starter uses a browser identity for demonstration; replace it with your authenticated user session before multi-user use.