Skip to main content

Documentation Index

Fetch the complete documentation index at: https://yanhgming.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Marlin handles the full lifecycle of a stablecoin payment — from creating a customer record to confirming settlement on Solana. This guide walks you through each step using the TypeScript SDK. By the end, your server creates an invoice, hands the customer a payment URL, and fulfills the order automatically when the payment is confirmed on-chain.
1

Install the SDK

Add @marlin/sdk to your project:
npm install @marlin/sdk
# or
pnpm add @marlin/sdk
Initialize the client with your secret API key. Keep this key server-side only — never expose it to the browser.
import { Marlin } from "@marlin/sdk";

const marlin = new Marlin({ apiKey: process.env.MARLIN_API_KEY! });
2

Create a customer

Every invoice belongs to a customer record. Create one with the customer’s email address and, optionally, their Solana wallet address.
const customer = await marlin.customers.create({
  email: "jane@example.com",
  name: "Jane Smith",
  walletAddress: "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
});

console.log(customer.id); // "cus_01HXYZ..."
Store the returned customer.id in your own database so you can associate future invoices and subscriptions with the same customer.
3

Create an invoice

Create an invoice with one or more line items. Set autoSend: true to have Marlin email the payment link to the customer automatically.
const invoice = await marlin.invoices.create({
  customerId: customer.id,
  currency: "USDC",
  dueDate: "2026-06-01",
  lineItems: [
    {
      description: "Pro Plan — May 2026",
      quantity: 1,
      unitAmount: 4900, // in cents: $49.00
    },
    {
      description: "Setup fee",
      quantity: 1,
      unitAmount: 2500, // $25.00
    },
  ],
  autoSend: true,
});

console.log(invoice.paymentUrl);
// https://checkout.marlin.fi/i/tok_...
The API returns a full Invoice object:
{
  "id": "inv_01HXYZ",
  "object": "invoice",
  "customerId": "cus_01HXYZ",
  "status": "open",
  "currency": "USDC",
  "amount": 7400,
  "amountPaid": 0,
  "amountRemaining": 7400,
  "dueDate": "2026-06-01",
  "lineItems": [
    { "description": "Pro Plan — May 2026", "quantity": 1, "unitAmount": 4900, "amount": 4900 },
    { "description": "Setup fee", "quantity": 1, "unitAmount": 2500, "amount": 2500 }
  ],
  "paymentUrl": "https://checkout.marlin.fi/i/tok_abc123",
  "transactionSignature": null,
  "createdAt": "2026-05-06T10:00:00Z"
}
autoSend: true triggers an email to the customer as soon as the invoice is created, using the email stored on the customer record. Omit the field (or set it to false) if you want to send the link manually or embed the widget instead.
4

Share the payment URL

The invoice.paymentUrl is a fully hosted checkout page at checkout.marlin.fi. Share it directly — by email, SMS, or in-app — and the customer can pay with any Solana wallet.
// Send via your own email or notification system
await sendEmail({
  to: customer.email,
  subject: `Your invoice for $${(invoice.amount / 100).toFixed(2)}`,
  body: `Pay here: ${invoice.paymentUrl}`,
});
The checkout page handles wallet connection, balance checks, transaction building, and on-chain confirmation automatically.
5

Listen for the invoice.paid webhook

When payment is confirmed on Solana, Marlin posts an invoice.paid event to your webhook endpoint. Use verifyWebhook from the SDK to validate the HMAC-SHA256 signature before processing.
import { verifyWebhook, MarlinAPIError } from "@marlin/sdk";

// Express example
app.post("/webhooks/marlin", express.raw({ type: "application/json" }), (req, res) => {
  let event;

  try {
    event = verifyWebhook({
      payload: req.body,
      signature: req.headers["marlin-signature"] as string,
      secret: process.env.MARLIN_WEBHOOK_SECRET!,
    });
  } catch {
    return res.status(400).send("Invalid signature");
  }

  if (event.type === "invoice.paid") {
    const invoice = event.data as Invoice;

    // Fulfill the order
    await fulfillOrder({
      invoiceId: invoice.id,
      customerId: invoice.customerId,
      txSignature: invoice.transactionSignature,
    });
  }

  res.status(200).send("OK");
});
Always verify the marlin-signature header before trusting the payload. Never fulfill orders based on unverified webhook data.
Configure your webhook URL and retrieve your signing secret from Dashboard → Settings → Webhooks.

Error handling

The SDK throws a MarlinAPIError for any non-2xx response. Check statusCode and code to handle specific cases:
import { MarlinAPIError } from "@marlin/sdk";

try {
  const invoice = await marlin.invoices.create({ ... });
} catch (err) {
  if (err instanceof MarlinAPIError) {
    console.error(err.statusCode, err.code, err.message);
  }
}