How-to

Test with faucets

Pay your own test checkouts with QBFT, QBitFlow's free test token, and watch the webhook land.

QBFT comes from QBitFlow's faucet, on Sepolia, Base Sepolia and Solana Devnet.

  1. Step 1: Switch to Test

    In the dashboard, flip the switch to Test: a test key, a test wallet, test endpoints. Add a wallet in Settings › Wallets and pick its currencies.

  2. Step 2: Get QBFT

    Open the QBFT faucet, signed in, pick a chain and paste a wallet you control: the one you'll pay from, not the one you receive on. Each wallet gets QBFT once per chain every 6 hours, and your space and your account (every space and organization together) each make at most 3 requests per test network, plus 1, every 24 hours: a marketplace funds its own wallets, then a few of its sellers'. A wrong address uses nothing up. The dashboard's test payment can also pay your own wallet, like a customer would.

  3. Step 3: Open a test checkout

    With your test key. A test price is at most $5, fees included: faucet tokens are scarce.

    Create a payment checkout

    checkout-create-payment.sh

    # A one-time checkout for an inline product. QBitFlow replaces {{UUID}} with the session's id on# redirect. The same Idempotency-Key on a retry never opens a second checkout.curl https://api.qbitflow.app/v2/checkout-sessions/payment \  -H "X-API-Key: $QBITFLOW_API_KEY" \  -H "Content-Type: application/json" \  -H "Idempotency-Key: $(uuidgen)" \  -d '{    "productName": "T-shirt",    "description": "Blue, size M",    "priceUsd": "4.99",    "reference": "order-1042",    "successUrl": "https://shop.example.com/orders/success?uuid={{UUID}}",    "cancelUrl": "https://shop.example.com/orders/cancel"  }'# 201: a CreatedCheckoutSession ({ "link": "https://…/checkout?uuid=pay@…", "uuid": "pay@…", "expiresAt": "…" })# Redirect the customer to "link".
    // An inline product: nothing needs to exist in your catalog. QBitFlow replaces {{UUID}} with the// session's id on the redirect.const session = await client.checkoutSessions.createPayment({	productName: 'T-shirt',	description: 'Blue, size M',	priceUsd: '4.99', // USD, a decimal string, never a float	reference: 'order-1042', // your order id: unique per space	successUrl: `https://shop.example.com/orders/success?uuid=${Placeholders.UUID}`,	cancelUrl: 'https://shop.example.com/orders/cancel',}); // Redirect the customer to the hosted checkout page.console.log(`Redirect the customer to ${session.link}`);
    session = client.checkout_sessions.create_payment(    product_name="T-shirt",    description="Blue, size M",    price_usd="4.99",  # USD, a decimal string: never a float    reference="order-1042",  # your order id: unique per space    # QBitFlow replaces the placeholder with the session's id when it redirects the customer.    success_url=f"https://shop.example.com/orders/success?uuid={qbitflow.PLACEHOLDER_UUID}",    cancel_url="https://shop.example.com/orders/cancel",)# Redirect the customer to the hosted checkout page.print("Send the customer to", session.link)
    // An inline product: no product needed in the catalog. QBitFlow replaces {{UUID}}// (qbitflow.PlaceholderUUID) with the session's id on redirect.session, err := client.CheckoutSessions.CreatePayment(ctx, &qbitflow.CreatePaymentSessionParams{	ProductName: "T-shirt",	Description: "Blue, size M",	PriceUSD:    "4.99",       // a decimal string, never a float	Reference:   "order-1042", // your order id: unique per space	SuccessURL:  "https://shop.example.com/orders/success?uuid=" + qbitflow.PlaceholderUUID,	CancelURL:   "https://shop.example.com/orders/cancel",})if err != nil {	log.Fatal(err)}// In an HTTP handler: http.Redirect(w, r, session.Link, http.StatusSeeOther)fmt.Println("Redirect the customer to", session.Link)
    $session = $client->checkoutSessions->createPayment(new \QBitFlow\Params\CreatePaymentSessionParams(	productName: 'T-shirt',	description: 'Blue, size M',	priceUsd: '4.99', // a decimal string, never a float	reference: 'order-1042', // your order id: unique per space	successUrl: 'https://shop.example.com/orders/success?uuid=' . \QBitFlow\Placeholders::UUID,	cancelUrl: 'https://shop.example.com/orders/cancel',)); header('Location: ' . $session->link); // send the customer to the hosted checkout
  4. Step 4: Pay it, watch it land

    Open the link, connect the wallet holding your QBFT and pay. Your endpoint receives payment.completed, exactly as in live.

    Wait for a checkout to finish

    wait-for-completion.sh

    # Waiting for a checkout means reading its status again (every few seconds) until it is completed# or expired. Webhooks are the way to fulfil orders.curl "https://api.qbitflow.app/v2/checkout-sessions/$SESSION_UUID" \  -H "X-API-Key: $QBITFLOW_API_KEY"# 200: a CheckoutSessionStatus
    // Polls get until the session is completed or expired. When the timeout comes first,// it returns the last status seen: check it.const final = await client.checkoutSessions.waitForCompletion(sessionUuid, {	timeout: 10 * 60_000, // ms	interval: 5_000, // ms, at least 1000});if (final.status === CheckoutSessionStatusValue.Completed) {	console.log(`paid, tx ${final.txHash}`);} else {	console.log(`not paid: ${final.status}`); // expired, or still open at the timeout}
    # For scripts and back-office jobs: fulfil orders on the payment.completed webhook. It polls# checkout_sessions.get; on timeout it returns the last status seen (check .status).final = client.checkout_sessions.wait_for_completion(session_uuid, timeout=600, interval=3)if final.status == qbitflow.CheckoutSessionStatusValue.COMPLETED:    print("paid")else:    print("not paid:", final.status)  # expired, or still open when the timeout elapsed
    // For scripts, tests and back-office jobs: orders are fulfilled on the webhook.status, err := client.CheckoutSessions.WaitForCompletion(ctx, sessionUUID, &qbitflow.WaitOptions{	Timeout:  2 * time.Minute, // default 10 minutes	Interval: 5 * time.Second, // default 3 s})if err != nil {	log.Fatal(err) // Get's errors, or the context's}if status.Status != qbitflow.CheckoutSessionStatusCompleted {	fmt.Println("not paid (yet):", status.Status) // expired, or still open at the timeout}
    // For scripts and back-office jobs: fulfil orders on the payment.completed webhook.$status = $client->checkoutSessions->waitForCompletion($sessionUuid, timeout: 600, interval: 3);if ($status->status === \QBitFlow\Enums\CheckoutSessionStatusValue::COMPLETED) {	echo "Paid, tx {$status->txHash}\n";} else {	echo "Not paid: {$status->status}\n"; // expired, or still pending when the timeout elapsed}

Test mode

Above $5, 400 validation_failed on the price (details.max is the cap). Test mode never emails your customers (test mode).

Test a subscription's bills

A live subscription is billed on its due date; a test one when you ask, so you see each bill and its webhook at your own pace:

Bill a test subscription now

subscriptions-test-bill.sh

# Test mode only: runs the next billing now, with live's statuses and webhooks.# 409 payment_not_due before nextBillingAt.curl -X POST "https://api.qbitflow.app/v2/subscriptions/$SUBSCRIPTION_UUID/test-bill" \  -H "X-API-Key: $QBITFLOW_API_KEY"# 200: a TestBill
// Test mode only: runs the next billing now, with live's statuses and webhooks. A ConflictError// payment_not_due before nextBillingAt; a BadRequestError test_mode_only for a live one.const bill = await client.subscriptions.testBill(subscriptionUuid);// billUuid is sub-hist@…: the id its subscription.billed carries once paid.console.log(bill.billUuid, bill.stage, bill.outcome ?? 'running', bill.failureCode ?? '');
# Test mode only: run the next bill now instead of on its due date.bill = client.subscriptions.test_bill(subscription_uuid)print(bill.bill_uuid, bill.stage, bill.outcome, bill.failure_code)  # sub-hist@…
// Test mode only: runs the next billing now, with live's statuses and webhooks.bill, err := client.Subscriptions.TestBill(ctx, subscriptionUUID)if err != nil {	log.Fatal(err) // 409 payment_not_due before NextBillingAt; 400 test_mode_only for a live one}fmt.Println(bill.BillUUID, bill.Stage, bill.Outcome, bill.FailureCode) // sub-hist@…
// Test mode only: runs the next billing now instead of on its due date.$bill = $client->subscriptions->testBill($subscriptionUuid);echo "Bill {$bill->billUuid}: {$bill->stage} " . ($bill->outcome ?? '-') . ' ' . ($bill->failureCode ?? '') . "\n"; // sub-hist@…

Other test tokens

The checkout may also offer test tokens from public faucets, listed on the faucets page. They're harder to come by: that's why QBFT exists.