Bag is live — accept USDC & card payments globally. Get started →
BagBag Docs
Getting Started

Quickstart

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

Go from zero to a confirmed test payment in under 10 minutes. Bag 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 Bag accountsign up at 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

Install the SDK

Bag has a first-party TypeScript SDK:

npm install @tbagtapp/sdk

No Python SDK yet — use requests to hit the REST API directly. Same endpoints, same behavior:

pip install requests

A payment link is a reusable checkout URL. You create one for each product or plan you sell, and Bag gives you a hosted checkout page your customers can pay through — no frontend work required.

import { Bag } from "@tbagtapp/sdk";

// Initialize the client with your test API key
const bag = new Bag({
  apiKey: process.env.BAG_API_KEY!,
});

async function main() {
  // Create a payment link for a $29.99 product on Base Sepolia (testnet)
  const link = await bag.paymentLinks.create({
    name: "Pro Plan",           // Display name shown on the checkout page
    amount: 29.99,              // Price in USD
    network: "base_sepolia",    // Testnet network — no real money moves
  });

  console.log("Payment link created:");
  console.log(`  ID:     ${link.id}`);
  console.log(`  Name:   ${link.name}`);
  console.log(`  Amount: $${link.amount} ${link.currency}`);
  console.log(`  Token:  ${link.token}`);
  console.log(`  Active: ${link.active}`);
  console.log(`  URL:    https://getbags.app/pay/${link.id}`);
}

main().catch(console.error);

Run it:

npx tsx create-link.ts
import os
import requests

API_KEY = os.environ["BAG_API_KEY"]
BASE_URL = "https://getbags.app"

# Create a payment link for a $29.99 product on Base Sepolia (testnet)
response = requests.post(
    f"{BASE_URL}/api/payment-links",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    },
    json={
        "name": "Pro Plan",           # Display name shown on the checkout page
        "amount": 29.99,              # Price in USD
        "network": "base_sepolia",    # Testnet network — no real money moves
    },
)

result = response.json()

if result["status"] == "success":
    link = result["data"]
    print("Payment link created:")
    print(f"  ID:     {link['id']}")
    print(f"  Name:   {link['name']}")
    print(f"  Amount: ${link['amount']} {link['currency']}")
    print(f"  Token:  {link['token']}")
    print(f"  Active: {link['active']}")
    print(f"  URL:    https://getbags.app/pay/{link['id']}")
else:
    print(f"Error: {result.get('message', 'Unknown error')}")

Run it:

python create_link.py

What you get back

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "Pro Plan",
  "amount": 29.99,
  "currency": "USD",
  "network": "base_sepolia",
  "token": "USDC",
  "active": true,
  "livemode": false,
  "merchantWalletAddress": "0xYourWalletAddress",
  "totalCollected": 0,
  "totalTransactions": 0,
  "createdAt": "2026-03-01T12:00:00.000Z",
  "updatedAt": "2026-03-01T12:00:00.000Z"
}
FieldWhat it means
idUnique identifier. Your checkout URL is https://getbags.app/pay/{id}
amountPrice in USD
currencyAlways USD (default)
networkThe blockchain network this link accepts payments on
tokenThe token customers pay with — defaults to USDC
activeWhether the link accepts new payments
livemodefalse for test keys, true for live keys
merchantWalletAddressThe wallet that receives funds
totalCollected / totalTransactionsRunning totals — both start at 0

Save the id. Your checkout page is live at https://getbags.app/pay/{id}. Share this URL with customers, embed it in your app, or redirect to it from a "Buy" button.

Handle the webhook

When a customer pays, the transaction confirms on-chain asynchronously. Bag 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
payment.completedPayment confirmed on-chainFulfill the order, grant access, send a receipt
payment.failedTransaction reverted or timed outNotify the customer, allow retry
subscription.createdRecurring payment subscription createdStore subscription ID, set up recurring access
subscription.updatedSubscription plan or details changedUpdate stored plan info
subscription.renewedRecurring payment succeededExtend access period
subscription.renewal_dueUpcoming renewal requires actionNotify customer, ensure funds available
subscription.past_dueRenewal payment failed, grace period activeWarn customer, retry or restrict access
subscription.canceledSubscription permanently canceledRevoke access at period end

Setup:

  1. Go to the Bag 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 in your browser: https://getbags.app/pay/YOUR_PAYMENT_LINK_ID
  2. Connect a wallet (MetaMask, Coinbase Wallet, or Phantom for Solana) on the correct testnet
  3. Approve the USDC transfer — the 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 payment.completed event logged
  6. Check the dashboard — the transaction appears under your payment link with full details

Expected webhook output

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

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

Verification checklist

  • Payment link created successfully
  • Webhook server running and reachable (via ngrok or deployed)
  • Checkout page loads at https://getbags.app/pay/YOUR_LINK_ID
  • Payment completes on testnet
  • Webhook received with valid signature
  • Transaction visible in dashboard

What's next


Quick reference

ResourceURL
Dashboardgetbags.app
SDK (npm)npm install @tbagtapp/sdk
API Base URLhttps://getbags.app
OpenAPI Specgetbags.app/openapi.yaml

On this page