BagsBags Docs
Getting Started

Quickstart

Go from zero to a confirmed test payment in under 10 minutes.

Bags is a Merchant of Record — we handle tax, compliance, invoicing, and settlement so you can focus on your product.


Before you start

You'll need three things:

  • Node.js 18+ (for TypeScript examples) or Python 3.8+ (for Python examples)
  • A Bags accountsign up at www.getbags.app (takes under 60 seconds)
  • A test API key — after signing up, go to Developer Settings in the dashboard and generate a test key

Your test key looks like this:

bag_test_sk_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6
  • Test keys (bag_test_sk_*) hit the sandbox. No real money moves. Use these while building.
  • Live keys (bag_live_sk_*) hit production. You'll need to complete KYB verification first.
  • Your full key is shown once at creation. Copy it and store it somewhere safe.

Set it as an environment variable so the examples below work as-is:

export BAG_API_KEY="bag_test_sk_your_key_here"

Never expose your API key in client-side code, public repos, or browser bundles. Treat it like a password. Use environment variables or a secrets manager.


The quickstart

Set up your environment

Bags is a plain REST API — any HTTP client works. Pick the native client for your language:

Node 18+ ships with a global fetch, so there's nothing to install:

node --version  # should be v18.0.0 or higher

Use requests to hit the REST API directly:

pip install requests

Create a product and checkout (golden path)

The primary integration path is V1 checkouts: create a catalog product once, then create a hosted checkout session per purchase.

  1. POST /api/v1/products — define what you're selling
  2. POST /api/v1/checkouts — get a one-time hosted URL (data.url) and session id for webhooks
  3. Redirect your customer to data.url

Sessions expire 30 minutes after creation if unpaid.

const API_KEY = process.env.BAG_API_KEY!;
const BASE_URL = "https://www.getbags.app";

async function main() {
  const productRes = await fetch(`${BASE_URL}/api/v1/products`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": crypto.randomUUID(),
    },
    body: JSON.stringify({
      name: "Pro Plan",
      amount: 29.99,
      network: "base_sepolia",
    }),
  });
  const product = await productRes.json();
  if (product.status !== "success") throw new Error(product.message);

  const checkoutRes = await fetch(`${BASE_URL}/api/v1/checkouts`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": crypto.randomUUID(),
    },
    body: JSON.stringify({
      productId: product.data.id,
      network: "base_sepolia",
      externalCustomerId: "cus_123",
      metadata: { orderId: "ord_456" },
    }),
  });
  const checkout = await checkoutRes.json();
  if (checkout.status !== "success") throw new Error(checkout.message);

  console.log("Checkout ready:");
  console.log(`  Session: ${checkout.data.id}`);
  console.log(`  URL:     ${checkout.data.url}`);
}

main().catch(console.error);
import os
import uuid
import requests

API_KEY = os.environ["BAG_API_KEY"]
BASE_URL = "https://www.getbags.app"
headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

product = requests.post(
    f"{BASE_URL}/api/v1/products",
    headers={**headers, "Idempotency-Key": str(uuid.uuid4())},
    json={"name": "Pro Plan", "amount": 29.99, "network": "base_sepolia"},
).json()
if product["status"] != "success":
    raise RuntimeError(product.get("message"))

checkout = requests.post(
    f"{BASE_URL}/api/v1/checkouts",
    headers={**headers, "Idempotency-Key": str(uuid.uuid4())},
    json={
        "productId": product["data"]["id"],
        "network": "base_sepolia",
        "externalCustomerId": "cus_123",
        "metadata": {"orderId": "ord_456"},
    },
).json()
if checkout["status"] != "success":
    raise RuntimeError(checkout.get("message"))

print("Checkout ready:")
print(f"  Session: {checkout['data']['id']}")
print(f"  URL:     {checkout['data']['url']}")

API reference: POST /api/v1/products · POST /api/v1/checkouts · GET /api/v1/checkouts/{id}.

Optional: payment links. If you prefer a reusable URL (/pay/{linkId}) shared across many customers, use POST /api/payment-links instead of per-session checkouts. See Create a Payment Link.

Endpoint cheat sheet: POST /api/v1/checkouts = merchant hosted session (this quickstart). POST /api/payment-links = reusable link. POST /api/checkout/session = buyer wallet step in custom checkout. See Choose Your Integration.

Handle the webhook

When a customer pays, the transaction confirms on-chain asynchronously. Bags sends a POST request to your server when it's done. Webhooks are the source of truth for payment status — never rely on client-side confirmation.

EventWhen it firesWhat to do
checkout.completedHosted checkout paid and confirmed on-chainFulfill the order, grant access, send a receipt
checkout.failedTransaction reverted or verification failedNotify the customer, surface the reason
checkout.cancelledCustomer cancelled before broadcastRelease holds, mark order cancelled
checkout.expiredCheckout not paid within 30 minutesRelease holds, optionally create a new checkout
payment.refundedRefund processed against a completed transactionReverse fulfillment, credit the customer

Use checkout.* events. They cover the full payment lifecycle and are keyed on sessionId, matching the id from POST /api/v1/checkouts. Subscription events are coming soon — see Webhook Events.

Setup:

  1. Go to the Bags dashboardDeveloper SettingsWebhooks
  2. Enter your server URL (e.g. https://yourapp.com/webhooks/bag)
  3. Copy the webhook secret (whsec_*) and store it as an environment variable
export BAG_WEBHOOK_SECRET="whsec_your_secret_here"

For local development, use ngrok to expose your localhost:

ngrok http 4000

See Handle Webhooks Securely for full implementation with signature verification, payload examples, retry behavior, and best practices.

Test it end-to-end

Everything you've built so far uses test mode. Here's how to run a full payment without spending real money.

Get testnet tokens

You need testnet USDC and native tokens for gas. See Environments for faucet links and network details.

Walk through a payment

  1. Open the checkout URL from step 2 (data.url) in your browser
  2. Connect a wallet (MetaMask, Coinbase Wallet, or Phantom for Solana) on the correct testnet
  3. Approve the USDC transfer — the hosted checkout UI walks you through it
  4. Wait for on-chain confirmation — usually a few seconds on testnets
  5. Check your webhook server — you should see a checkout.completed event logged
  6. Check the dashboard — the transaction appears with full details

Expected webhook output

When the payment confirms, your webhook server logs something like:

Received event: checkout.completed
Timestamp: 2026-03-01T12:05:00.000Z
Checkout completed: 29.99 USDC on base_sepolia
  Session: a1b2c3d4-e5f6-7890-abcd-ef1234567890
  Tx Hash: 0xabc123def456789...

Verification checklist

  • Product and checkout created successfully
  • Webhook server running and reachable (via ngrok or deployed)
  • Hosted checkout page loads at data.url
  • Payment completes on testnet
  • Webhook received with valid signature
  • Transaction visible in dashboard

What's next


Quick reference

ResourceURL
Dashboardgetbags.app
API Base URLhttps://www.getbags.app
OpenAPI Specgetbags.app/openapi.yaml

On this page