Paybrain
PricingDocs
Sign inGet started
⌘K
Docs/Developers

Checkout API

The Checkout API lets any website or app take a payment through Paybrain. You create a checkout with the amount and the buyer's email, send the buyer to the checkoutUrl Paybrain gives back, and Paybrain hosts the payment page. When the payment goes through, Paybrain posts a signed webhook to your server and sends the buyer back to your site.

Use it when there's no plugin for your store. For WooCommerce, use the WooCommerce plugin instead.

Premium only

API keys are a Premium feature. On Basic you can't create keys, and existing keys stop working until you upgrade again. See Plans and fees.

Before you start

  • Your Paybrain account is on Premium.
  • Stripe is connected. Without it, creating a checkout fails.
  • Your returnUrl, cancelUrl and webhookUrl use HTTPS.
  • Amounts are in cents, and the currency is USD. Other currencies are rejected.

Create an API key

  1. Open API Keys

    On the web, go to Settings›API Keys and click Manage Keys.

  2. Create the key

    Click Create API Key, give it a Key Name you'll recognize, such as "Production API", and click Create Key.

  3. Copy it now

    The full key is shown once. Copy it and store it on your server, for example in an environment variable. Keys start with pbk_.

The list shows each key's last four characters, when it was created and when it was last used. Use the switch to deactivate a key without deleting it, or delete it to revoke it for good. If a key is lost, create a new one.

Heads up

Treat API keys like passwords. Only call the API from your server, never from a browser or app where the key could be read.

Authentication

Send the key in the Authorization header on every request.

Authorization: Bearer pbk_…
Content-Type: application/json

All endpoints are on https://api.paybrain.io. The checkoutUrl you send buyers to is on paybrain.io.

Create a checkout

POST /v1/checkouts

curl -X POST https://api.paybrain.io/v1/checkouts \
  -H "Authorization: Bearer $PAYBRAIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 4999,
    "returnUrl": "https://shop.example.com/order/complete",
    "customer": { "email": "buyer@example.com", "name": "Jane Buyer" },
    "description": "Order #1234",
    "webhookUrl": "https://shop.example.com/webhooks/paybrain",
    "metadata": { "orderId": "1234" }
  }'

Request body

FieldTypeRequiredWhat it does
amountintegerYesThe subtotal in cents. Must be more than 0. Tax is added on top.
currencystringNoDefaults to USD. Only USD is accepted.
returnUrlstringYesHTTPS URL the buyer comes back to after paying.
cancelUrlstringNoHTTPS URL for a cancelled payment. Defaults to returnUrl.
customer.emailstringYesThe buyer's email. The receipt goes here.
customer.namestringNoThe buyer's name.
customer.phonestringNoThe buyer's phone number.
customer.billingAddressaddressNoSee Addresses.
descriptionstringNoThe line shown on the payment page. Defaults to "Order from" and your username.
subtitlestringNoA second line under description on the payment page, up to 200 characters.
shippingAddressaddressNoWhere the order ships. Adds a fulfillment panel to the order in Paybrain.
shippingAmountintegerNoHow much of amount is shipping, in cents. Shown as its own Shipping line. Must be less than amount. The total doesn't change.
shareShippingWithStripebooleanNoDefaults to true. Attaches the shipping address, with customer.name and customer.phone, to the Stripe payment, which helps with disputes and fraud checks. Only happens when you send customer.name. Set false to keep the address in Paybrain only.
lineItemsarrayNoItemized lines for the receipt. See Receipt lines.
receiptNotestringNoA note printed on the receipt and in its email, up to 280 characters.
webhookUrlstringNoHTTPS URL that gets a signed event when this checkout is paid or fails.
metadataobjectNoAnything you want back, such as your order ID. Returned on lookups and webhooks.

Addresses

{
  "line1": "456 Oak Ave",
  "line2": "Apt 4B",
  "city": "Austin",
  "state": "TX",
  "postalCode": "78704",
  "country": "US"
}

Every field is required except line2. country is a two-letter code.

Receipt lines

Each line has a name (up to 120 characters), a whole-number quantity from 1 to 999, a unitAmount in cents (0 or more) and an optional description. Send 1 to 50 lines.

The lines must add up to amount exactly (quantity × unitAmount, summed), or the request is rejected. Put shipping in as its own line. A line can't be negative, so price items after any discount. Without lineItems, the receipt shows your description as one line.

Response

{
  "shortId": "abc123xyz",
  "listingId": "65f…",
  "checkoutUrl": "https://paybrain.io/pay/redirect?shortId=abc123xyz&…",
  "expiresAt": "2026-10-19T12:00:00.000Z",
  "amount": 4999,
  "currency": "USD",
  "taxAmount": 412,
  "totalAmount": 5411,
  "webhookSecret": "pbwh_…"
}
FieldWhat it is
shortIdThe checkout's ID. Use it to look the checkout up.
listingIdPaybrain's internal ID, useful when you contact support.
checkoutUrlRedirect the buyer here.
amountThe subtotal you sent, in cents.
taxAmountWhat Paybrain added on top of amount, in cents: sales tax and, if you surcharge, the card surcharge.
totalAmountWhat the buyer will be charged, in cents.
webhookSecretOnly when you sent a webhookUrl. Returned once. Save it to verify webhooks.

Tax and surcharges

Checkouts follow the same settings as your other sales. If sales tax is on for your account, it's added to amount. If you add a card surcharge, the buyer sees it as its own line on the payment page.

What reaches Stripe

Stripe gets the buyer's email, your description and subtitle, and the shipping address with the buyer's name and phone unless shareShippingWithStripe is false. lineItems, receiptNote and metadata stay in Paybrain. If item names are sensitive, put them in lineItems and keep description generic, such as "Order #1234".

Look up a checkout

GET /v1/checkouts/:shortId

Use this to confirm a payment before you fulfill an order, or if you missed a webhook.

curl https://api.paybrain.io/v1/checkouts/abc123xyz \
  -H "Authorization: Bearer $PAYBRAIN_API_KEY"
{
  "shortId": "abc123xyz",
  "listingId": "65f…",
  "status": "paid",
  "paymentIntentId": "pi_…",
  "amount": 4999,
  "currency": "USD",
  "taxAmount": 412,
  "totalAmount": 5411,
  "customer": { "email": "buyer@example.com", "name": "Jane Buyer" },
  "shippingAddress": { "line1": "456 Oak Ave", "city": "Austin", "state": "TX", "postalCode": "78704", "country": "US" },
  "description": "Order #1234",
  "createdAt": "2026-10-18T11:00:00.000Z",
  "paidAt": "2026-10-18T11:03:42.000Z",
  "metadata": { "orderId": "1234" }
}
statusMeaning
pendingNot paid yet
paidPaid. A partly refunded checkout stays paid.
failedThe payment failed
refundedFully refunded

shippingAddress is only there if you sent one. paidAt is only there once the checkout is paid.

Count paid checkouts

GET /v1/checkouts/summary?metadataKey=…&metadataValue=…

Counts your paid checkouts whose metadata[metadataKey] equals metadataValue, and adds up a numeric metadata.units on each. Use it to cap how many of something you sell, such as seats in a class or a limited batch.

curl "https://api.paybrain.io/v1/checkouts/summary?metadataKey=batch&metadataValue=2026-11" \
  -H "Authorization: Bearer $PAYBRAIN_API_KEY"
{ "paidCheckouts": 7, "paidUnits": 9 }

metadataKey can use letters, numbers and _, up to 64 characters. metadataValue is matched exactly, as text.

Webhooks

If you send a webhookUrl, Paybrain posts JSON to it when the payment resolves.

EventWhen
checkout.paidThe buyer's payment succeeded
checkout.failedThe payment attempt failed
{
  "type": "checkout.paid",
  "shortId": "abc123xyz",
  "listingId": "65f…",
  "paymentIntentId": "pi_…",
  "amount": 4999,
  "taxAmount": 412,
  "totalAmount": 5411,
  "currency": "USD",
  "occurredAt": "2026-10-18T11:03:42.000Z",
  "metadata": { "orderId": "1234" }
}

Paybrain waits up to 8 seconds for your reply. Failed or non-2xx deliveries are not retried, so if you don't receive one, look the checkout up with GET /v1/checkouts/:shortId. Make your handler safe to run twice for the same shortId and type.

Verify the signature

Each delivery has a Paybrain-Signature header:

Paybrain-Signature: t=1760785422000,v1=5f2b…

t is the time it was signed, in Unix milliseconds. v1 is the hex HMAC-SHA256 of `${t}.${rawBody}`, keyed with that checkout's webhookSecret. Compute it over the raw request body. Parsing and re-serializing the JSON changes the bytes and breaks the match.

// Node.js
const crypto = require('crypto');

function verifyPaybrainSignature(rawBody, header, secret, toleranceMs = 5 * 60 * 1000) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')));
  const t = parseInt(parts.t, 10);
  if (!t || Math.abs(Date.now() - t) > toleranceMs) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  const given = Buffer.from(parts.v1 || '', 'hex');
  return given.length === 32 && crypto.timingSafeEqual(Buffer.from(expected, 'hex'), given);
}
# Python
import hashlib, hmac, time

def verify_paybrain_signature(raw_body: bytes, header: str, secret: str, tolerance_ms: int = 5 * 60 * 1000) -> bool:
    parts = dict(p.strip().split('=', 1) for p in header.split(','))
    try:
        t = int(parts['t'])
    except (KeyError, ValueError):
        return False
    if abs(int(time.time() * 1000) - t) > tolerance_ms:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get('v1', ''))

The 5-minute tolerance is a suggestion. Pick what suits you.

The buyer's return

After paying, the buyer goes back to your returnUrl through a one-time Paybrain link, so a success URL from browser history can't be replayed. Paybrain adds two query parameters:

ParameterValue
paybrain_return1
paybrain_statuspaid once Paybrain has seen the payment, otherwise unpaid
Heads up

Anyone can type these parameters into a URL. Use them to choose what page to show, never to mark an order paid. Wait for the checkout.paid webhook, or look the checkout up, first.

Errors

StatusWhen
400The request was invalid, for example a missing customer.email, a non-HTTPS URL, a currency other than USD, or lineItems that don't add up to amount. Stripe not connected also returns 400.
404No checkout with that shortId on your account
410The key is missing, malformed, inactive or deleted, or the account is on Basic. The body is a message saying which.
500Something went wrong on our side. Try again.

Related

WooCommerce plugin Sales tax Plans and fees
PreviousSettings and notificationsNext WooCommerce plugin
Paybrain

Products

Paybrain RegisterTap to PayInvoicing & RecurringPaybrain MobilePaybrain ProviderAll hardware

Industries

Restaurants & cafésRetailSalons & med-spasGyms & clubsProfessionalsOnline

Company

PricingDocsSupport

Legal

WarrantyReturnsShippingTermsPrivacyAnti-fraud

© 2026 Paybrain. All rights reserved.

Payments powered by Stripe