Webhook events
What QBitFlow posts to your endpoints: every event, its body, its signature and its retries.
A payment confirmed, a subscription billed, a refund answered, a seller joined: QBitFlow posts an
event to your endpoints (POST /webhooks/
payment.completed· the body your endpoint receives
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "amount": "10", "reference": "order-1042", "uuid": "pay@01a0f755-f200-7000-8000-000000000001", … 25 more fields }, "id": "evt_82c1e15d-c0d9-584f-a65a-fb4099925273", "test": false, "type": "payment.completed", "version": "v2"}data: Depends on the type: each event below gives its fields and an example.id: The same for every endpoint and every retry: deduplicate on it.test: Whether it happened in test mode.type: What happened. Answer2xxto the types you don't handle.
Avoid
Act on the redirect
Do
Act on the event
id, answer 2xx fast.Who receives an event
- An event happens in one space and mode (spaces). It goes to that space's endpoints that list its type (or list none: every type).
- A member's events also go to the organization's endpoints with
includeMembers: true(same mode), naming the member in the envelope'suserUuid. A new organization endpoint includes them by default: a marketplace receives every event of each seller's space,payment.completed, everysubscription.*(subscription.actionRequiredChangedincluded),refund.*,heldFunds.released(the organization triggers a release, the event is the seller's) andcheckout.expired, on its own endpoint, with nothing to set up per seller; create it withincludeMembers: falseto receive the organization's own events only.member.joinedandmember.removedare the organization's own events: its endpoints get them with or withoutincludeMembers. - Test and live never mix: a test-mode event only reaches test-mode endpoints.
- A member's own endpoints receive nothing while the organization's
members.webhookspolicy is off for them: each event is logged as skipped (skipped: truein the delivery log), never retried, and not counted against the endpoint's health (Webhooks § Access). Turned back on, the next events are delivered.
The envelope
The envelope
{ "id": "evt_0192f1c2-…", "type": "payment.completed", "version": "v2", "createdAt": "2026-10-01T12:00:00.123Z", "test": false, "userUuid": "0192f1c2-7b3a-7c4d-9e5f-6a7b8c9d0e1f", "data": {}}idstringevt_…: the same for every endpoint and every retry of the event, and if it is ever published again (deduplicate on it)
typestringOne of
payment.completedsubscription.createdsubscription.billedsubscription.statusChangedsubscription.actionRequiredChangedsubscription.billingFailedsubscription.upcomingBillrefund.requestedrefund.approvedrefund.rejectedmember.joinedmember.removedheldFunds.releasedcheckout.expiredwebhook.testWebhookTest is the event POST /webhooks/endpoints/:uuid/test sends; no endpoint subscribes to it.
versionstringv2
One of:
v2V2 is the event envelope: {id, type, version, createdAt, test, userUuid, data}. The only version today: the field exists so that a future payload change is a new version an endpoint opts into.
createdAtstringtestbooleanWhether it happened in test mode
userUuidstringOptionalThe member whose space it happened in; omitted for the organization's own
dataany JSON valueDepends on the type (GET /webhooks/event-types)
Events
payment.completed#
What it carries, and when
CompletedPayment: the payment as GET / returns it to its owner (amount + amountMinUnits in the token, amountUsd, priceUsd, fees, productName, productDescription, currency, settlement, paid*, metadata copied from its checkout, chain, explorerUrl: its transaction on the chain's explorer), and its customer page managementPageLink. The refund state (refund, refundable) is API reads only
What to do · One-time payment
Fulfil the order. data.uuid is the checkout session's pay@… (from POST /), data.reference your reference, data.metadata what you put on the checkout, data. the customer.
34 fields of data (CompletedPayment)
uuidstringallow read and create, (generated on creation of the payment link)
A prefixed id (pay@…, sub@…)
createdAtstringfromstringtostringamountstringThe amount in the currency's units, exact, a decimal string ("10" for 10 USDC): amountMinUnits / 10^decimals
amountMinUnitsstringThe amount in the currency's smallest unit, a string of digits ("10000000" for 10 USDC, 6 decimals)
amountUsdstringWhat the customer paid, in USD, a decimal string: the price (and, on a one-time payment, the fees its checkout added: fees), which the contracts split between QBitFlow's fee (the referrer's share included), the organization's fee and the merchant (settlement.split). The network fee a customer pays on top (settlement.split.networkFee) is not in it
currencyIdintegerThe currency paid in, written as the currency object under currency (GET /currencies/:id)
txHashstringHash of the transaction in the blockchain
chainstringOptionalThe chain it was paid on: ETH (Ethereum), BASE (Base) or SOL (Solana), the testnet's for a test-mode transfer (currency.test)
explorerUrlstringOptionalIts transaction on the chain's block explorer (the testnet's in test mode). Omitted without a txHash
testbooleanWhether this is test data (testnets) or live data
userUuidstringOptionalMember the resource belongs to; omitted for the organization's own resources
Only for the resource's owner
referencestringOptionalThe merchant's reference for the payment (an order or invoice id), set when creating its checkout. Only for the merchant: never in the public reads by uuid (GET /hosted/transactions/:txUuid)
Only for the resource's owner
priceUsdstringOptionalPriceUSD is the product's price in USD, as the checkout had it, a decimal string, always present: amountUsd is the price plus the fees' lines. (A payment recorded before checkouts had fees has none stored: its price is its amount, Complete.)
feesarray of FeeLineWhat the checkout added to the price, line by line, as the customer saw them (a tax, shipping, the processing fee when the customer paid it); [] without any
4 fields of each
FeeLinetypestringcustom (the merchant's line) or processingFee (QBitFlow's fee, which the merchant has the customer pay)
One of:
customThe merchant's own line: a tax, shipping, a service feeprocessingFeeQBitFlow's fee, which the merchant has the customer pay (computed by QBitFlow)
labelstringThe line's name, as the checkout shows it ("Processing fee" for the processing fee)
descriptionstringOptionalOptional: more about the line, as the merchant wrote it. Omitted without one
amountUsdstringThe line's amount in USD, a decimal string with at most 2 decimals
productNamestringWhat was paid for: the checkout's product name, as it was when the checkout was created (the product's, or the inline one). Later product edits don't change it
productDescriptionstringOptionalSame, for the product's description; absent without one
metadatamapOptionalMetadata is the merchant's own data, copied from the checkout session that created the payment (its metadata), read-only. {} without any. Only for the merchant
Only for the resource's owner
productUuidstringOptionalThe product paid for; omitted when the payment has none (an inline product)
customerUuidstringOptionalThe customer, when QBitFlow has one (named by the checkout, found by its customerReference, or recorded from the details the customer gave); omitted otherwise
Only for the resource's owner
customerReferencestringOptionalThe merchant's reference of the customer, as the checkout was given it (customerReference), whether or not QBitFlow has a customer with it; omitted otherwise
Only for the resource's owner
notestringOptionalA qbf.cash tipper's message to the handle's owner (only for them); omitted without one
Only for the resource's owner
customerSummaryOptionalThe customer, named (merchant only, API reads): who paid, even once deleted. Omitted without one
Only for the resource's owner
6 fields of
SummaryuuidstringThe customer (GET /customers/:uuid)
firstNamestringFirst name
lastNamestringOptionalLast name, when given
emailstringEmail, lowercase
referencestringOptionalThe merchant's reference of the customer, when it has one
deletedbooleanThe merchant deleted the customer: it still names its rows, but GET /customers/:uuid answers 404
settlementSettlementOptionalHow it settled: its fees, its split and its transaction (only for the merchant)
Only for the resource's owner
5 fields of
SettlementfeePercentnumberQBitFlow's fee on this payment, in bps (1 % = 100 bps), deducted from the amount paid. Written as feePercent, a percent string
organizationFeeOrganizationFeeOptionalThe organization's fee on its member's payment (a marketplace's commission), paid to the organization's wallet. It is taken from what remains after QBitFlow's fee (contracts SPEC §4.1): organization = (amount - QBitFlow's fee) × its rate, and the member receives the rest. While the organization holds the member's funds, it is withheld from what the organization owes them instead of paid on-chain. Absent: none
2 fields of
OrganizationFeeaddressstringOptionalThe organization's wallet that receives the fee (only for the merchant)
Only for the resource's owner
feePercentnumberThe organization's fee, in bps, taken from what remains after the platform fee. Written as feePercent, a percent string
referralFeeReferralFeeOptionalThe referrer's share of QBitFlow's fee. Absent: none
2 fields of
ReferralFeeaddressstringOptionalThe referrer's wallet that receives the fee (only for the merchant)
Only for the resource's owner
feePercentnumberThe referrer's share of the platform fee, in bps (2000 = 20 % of it). Written as feePercent, a percent string
splitSplitOptionalHow the contracts split what the payer paid. For a payment the organization held for its member, the organization's fee was withheld from the member's share (not paid on-chain): the split is the one a direct payment would have had. Absent on records made before the split was recorded
5 fields of
SplitplatformTokenAmountQBitFlow's fee (the referrer's share not included)
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
organizationTokenAmountOptionalThe organization's fee; absent without one
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
referralTokenAmountOptionalThe referrer's share of QBitFlow's fee; absent without one
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
merchantTokenAmountWhat the merchant received (the member, for a member's payment), net of every fee
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
networkFeeTokenAmountOptionalNetworkFee is the network fee the payer paid on top of the amount, in the payment's currency: the gas refund the contracts took in the token (their GasRefundProcessed event), or, for a payment in a chain's native coin, the fee of the transaction the payer's wallet paid (its gas and, on Base, its L1 data fee), bounded by what QBitFlow prepared. Not the transaction's own cost to its sender (transaction.fee). Absent on records made before it was recorded ("0" once recorded as none)
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
transactionSettlementTransactionOptionalThe transaction that paid it. Absent before its confirmation
6 fields of
SettlementTransactionblockNumberstringThe block (Ethereum, Base) or slot (Solana) it landed in, a string of digits
blockTimestringOptionalWhen its block was produced (UTC); when it was recorded if the chain's node didn't give it. Absent only on records made before it was recorded
gasUsedintegerOptionalThe gas it consumed (Ethereum, Base). Absent on Solana
feeTokenAmountWhat the transaction cost its sender, in the chain's native coin (ETH, SOL; its currency is the payment currency's chain): its gas and, on Base, its L1 data fee. amountUsd at nativePriceUsd
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
l1FeeTokenAmountOptionalThe part of fee that paid Base's L1 data fee, in the native coin. Absent off Base, and on transactions recorded before it was
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
nativePriceUsdstringOptionalNativePriceUSD is the USD price of the chain's native coin when the transaction was recorded (what its fee cost in USD). Absent when the price wasn't available then
confirmedAtstringOptionalWhen the chain confirmed it: its block's time (settlement.transaction.blockTime). Absent only on records made before it was recorded
paidstringOptionalEverything the customer's wallet sent, in the currency's units (a decimal string): the amount plus the network fee paid on top (settlement.split). A refund's base (refundPercent of it). Only for the merchant; absent when the split wasn't recorded
Only for the resource's owner
paidMinUnitsstringOptionalThe same in the currency's smallest unit, a string of digits. Only for the merchant, like paid
Only for the resource's owner
paidUsdstringOptionalThe same in USD, at the payment's time, a decimal string. Only for the merchant, like paid
Only for the resource's owner
refundRefundSummaryOptionalIts refund (one per transaction); omitted without one. The merchant's own refund appears once its transfer confirmed (approved)
Only for the resource's owner
9 fields of
RefundSummaryuuidstringThe refund (refund@…)
statusstringpending, approved or rejected
One of
pendingapprovedrejectedfailedDeclared, never set: an approval that fails leaves the refund pending, to approve again
initiatedBystringcustomer (a request) or merchant
refundPercentnumberThe share of what the customer paid it sends back, a percent string ("100": all of it). Written as refundPercent
amountstringOptionalWhat it sends back, in the currency's units (a decimal string). Absent when its currency isn't known (a refund requested before refunds named it)
amountMinUnitsstringThe same in the currency's smallest unit (a string of digits)
amountUsdstringOptionalThe same in USD, at the payment's time (a decimal string). Absent when unknown (a refund requested before it was recorded)
createdAtstringWhen it was requested or started
respondedAtstringOptionalWhen it was approved (confirmed) or rejected; absent while pending
refundablebooleanOptionalWhether it can be refunded now
Only for the resource's owner
notRefundableReasonstringOptionalWhy not: refundExists or heldFundsReleased. Omitted when refundable
Only for the resource's owner
checkoutOpenedAtstringOptionalWhen its checkout session was created (the payment's uuid is the session's). Omitted for a payment whose uuid doesn't say (made before UUIDv7s)
managementPageLinkstringOptionalThe payment's page for its customer
payment.completed: example body
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "amount": "10", "amountMinUnits": "10000000", "amountUsd": "10", "chain": "BASE", "checkoutOpenedAt": "2026-10-01T12:00:00Z", "confirmedAt": "2026-10-01T11:59:58Z", "createdAt": "2026-10-01T12:00:00Z", "currency": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false }, "customerReference": "crm-5521", "customerUuid": "01a05cd7-2a00-7d00-8000-0000000000d1", "explorerUrl": "https://basescan.org/tx/0x1111111111111111111111111111111111111111111111111111111111111111", "fees": [], "from": "0xabcdef1234567890abcdef1234567890abcdef12", "managementPageLink": "https://qbitflow.app/user/019cadfd-8900-7a00-8000-0000000000a1/manage?uuid=pay@01a0f755-f200-7000-8000-000000000001", "metadata": { "crm:customer": "C-5521", "orderId": "order-1042" }, "paid": "10.0042", "paidMinUnits": "10004200", "paidUsd": "10.0042", "priceUsd": "10", "productDescription": "Lifetime access to the Pro plan", "productName": "Pro plan (lifetime)", "productUuid": "019cadfd-8900-7c00-8000-0000000000c1", "reference": "order-1042", "settlement": { "feePercent": "0.99", "split": { "merchant": { "amount": "9.901", "amountMinUnits": "9901000", "amountUsd": "9.901" }, "networkFee": { "amount": "0.0042", "amountMinUnits": "4200", "amountUsd": "0.0042" }, "platform": { "amount": "0.099", "amountMinUnits": "99000", "amountUsd": "0.099" } }, "transaction": { "blockNumber": "52468103", "blockTime": "2026-10-01T11:59:58Z", "fee": { "amount": "0.00000114", "amountMinUnits": "1140000000000", "amountUsd": "0.005144" }, "gasUsed": 95000, "nativePriceUsd": "4512.37" } }, "test": false, "to": "0x1234567890abcdef1234567890abcdef12345678", "txHash": "0x1111111111111111111111111111111111111111111111111111111111111111", "uuid": "pay@01a0f755-f200-7000-8000-000000000001" }, "id": "evt_82c1e15d-c0d9-584f-a65a-fb4099925273", "test": false, "type": "payment.completed", "version": "v2"}Show all 80 linesShow less
subscription.created#
What it carries, and when
Subscription, as GET / returns it (its managementPageLink, metadata and allowance included). For a checkout without a trial it is sent once the customer's on-chain transaction is confirmed, with status: "active" (and actionRequired: "topUpAllowance" when the allowance signed covers only that first period: no subscription. at creation, this event tells it) and its first subscription. right after; a free trial starts one too, once its customer signed the trial's message (status: "trial", nothing paid yet, no allowance). Sent once per subscription: a trial confirmed later stays the same subscription and sends subscription. (trial or trialExpired → active) then its first subscription., never a second subscription.. The whole timeline: Subscriptions
What to do · Subscription start
status: "active": grant access until currentPeriodEnd (if actionRequired is set, ask the customer to act); store data.uuid (sub@…, the session's id) and managementPageLink. "trial": grant trial access. Its first subscription. follows (not for a trial). Subscriptions
29 fields of data (Subscription)
uuidstringIts id (sub@…): the checkout session's
A prefixed id (pay@…, sub@…)
referencestringOptionalThe merchant's reference for the subscription (an order or customer id), set when creating its checkout. Only for the merchant: never in the public reads by uuid (GET /hosted/transactions/:txUuid)
Only for the resource's owner
createdAtstringupdatedAtstringIts last change (a bill, a status, its metadata)
fromstringThe subscriber's wallet: the one it bills. Absent when unknown (a trial not confirmed yet may have none)
tostringThe wallet its bills pay: the merchant's (or, while the organization holds a member's funds, the organization's)
productUuidstringIts product (GET /products/:uuid). Absent without one
subscriptionHashstringThe hash of the transaction that created the subscription on-chain. Absent until then (a trial, until its customer confirms it)
currencyIdintegerThe currency (token) billed, written as the currency object under currency (GET /currencies/:id)
frequencyDurationBilling interval in seconds, as on-chain (e.g. 2592000 = 30 days). Written as a types.Duration in responses and webhooks (e.g. {"value": 1, "unit": "months"})
2 fields of
DurationvalueintegerNumber of units; 0 means no duration
unitstringRequired when value > 0. "months" = 30 days, "years" = 365 days
oneof=seconds minutes hours days weeks months years
statusstringSubscriptionStatus (status) is where it stands: trial, trialExpired, active, pastDue, paused, stopped or cancelled. Every change sends subscription.statusChanged. Don't grant access from it: grant access while now < accessUntil (docs/api/guides/subscriptions.md § Access)
One of:
trialStatusTrial is a free trial: nothing was signed on-chain yet.trialExpiredStatusTrialExpired is a trial that ended without its customer confirming it (actionRequired confirmTrial): cancelled after the grace period unless they subscribe.activeStatusActive is a subscription billed on its due dates. Its customer may have something to do (actionRequired: topUpAllowance, raiseMaximum) while it stays active.pastDueStatusPastDue indicates a subscription whose last bill failed: its retries run.pausedStatusPaused is a subscription its customer paused (a message signed with its wallet): QBitFlow doesn't bill it until they resume it. On-chain it is still active. It keeps the period it paid for (currentPeriodEnd).stoppedStatusStopped is a subscription cancelled during its billing period (by its customer, or its merchant's force cancel with immediate=false): never billed again, it still runs until its paid period ends (nextBillingDate), then it is cancelled.cancelledStatusCancelled indicates a cancelled subscription.
actionRequiredstringOptionalActionRequired is what its customer must do, if anything (absent: nothing): topUpAllowance (the allowance covers about one more bill at most), raiseMaximum (a bill waits for a new maximum per period), confirmTrial (the trial ended unconfirmed). Every change (set, changed or cleared) sends subscription.actionRequiredChanged
One of
topUpAllowanceActionTopUpAllowance: what remains of the allowance covers about one more bill at most (or none). The customer adds periods from their management page.raiseMaximumActionRaiseMaximum: a bill is above the maximum per period the customer signed (the token's price moved); it waits up to 7 days for a new maximum, signed from the management page.confirmTrialActionConfirmTrial: the trial ended unconfirmed (trialExpired); the customer subscribes from their management page before the grace period ends.
currentPeriodEndstringOptionalCurrentPeriodEnd is the paid-through date. A trial: its end. active, paused, stopped: the end of the period paid for (the next bill's due date). pastDue: the start of the unpaid period (past). cancelled: when it was cancelled, or the end of its last paid period if that came first. For access, read accessUntil: an active subscription is renewed a few minutes after this date
accessUntilstringOptionalAccessUntil is when access ends: grant it while now < accessUntil, whatever the status. An active subscription: currentPeriodEnd plus 1 hour, since its renewal is billed within minutes of its due date (a subscriber whose bill is on its way keeps access; a failed bill sets pastDue). Every other status: currentPeriodEnd
priceUsdstringPriceUSD is the price per period the customer subscribed at, in USD (a decimal string, e.g. "9.99"), locked at signup (a free trial's too): every bill charges it and its lines (fees), converted to the token at the bill's price. A later change of the product's price applies to new subscribers only. Never "0"
feesarray of FeeLineFees is what its checkout added to the price, line by line, as the customer saw them (a tax, shipping, the processing fee when the customer pays it), locked at signup with the price: every bill charges priceUsd plus these lines. [] without any
4 fields of each
FeeLinetypestringcustom (the merchant's line) or processingFee (QBitFlow's fee, which the merchant has the customer pay)
One of:
customThe merchant's own line: a tax, shipping, a service feeprocessingFeeQBitFlow's fee, which the merchant has the customer pay (computed by QBitFlow)
labelstringThe line's name, as the checkout shows it ("Processing fee" for the processing fee)
descriptionstringOptionalOptional: more about the line, as the merchant wrote it. Omitted without one
amountUsdstringThe line's amount in USD, a decimal string with at most 2 decimals
allowanceAllowanceOptionalAllowanceStatus (allowance) is what the subscriber's wallet still lets QBitFlow bill, and the most one bill may charge, at today's price. Absent during a free trial (nothing signed yet)
6 fields of
AllowanceamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
remainingPeriodsintegerOptionalRemainingPeriods is how many whole bills the allowance still covers at today's price: the allowance divided by a bill (priceUsd and its fees, converted to the token), rounded down, before the network fee each bill adds. 0 is a value: the customer must top it up (actionRequired topUpAllowance) before the next bill. Absent without a price
maxPerPeriodstringOptionalMaxPerPeriod is the most one bill may charge, as the subscriber signed it, in the token's units (maxPerPeriodMinUnits: its on-chain integer): a bill above it waits for a new maximum (actionRequired raiseMaximum). Absent when the subscription has none recorded
maxPerPeriodMinUnitsstringOptional
lastBilledAtstringOptionalWhen it was last billed. Absent before its first bill (a trial)
nextBillingAtstringOptionalWhen its next bill is due (a trial: when it ends; stopped: when it is cancelled). Absent once cancelled
cancelledAtstringOptionalWhen it was cancelled (absent until then, and for subscriptions cancelled before it was recorded)
cancellationReasonstringOptionalWhy it stopped or was cancelled: customer, merchant, billingFailed, trialNotConverted, merchantClosed or inactiveOnChain. Absent while it runs (and for subscriptions cancelled before it was recorded)
One of:
customerCancelledByCustomer: its customer cancelled it (it was stopped until its period ended).merchantCancelledByMerchant: its merchant cancelled it (POST /subscriptions/:uuid/cancel).billingFailedCancelledBillingFailed: its bill's last attempt failed.trialNotConvertedCancelledTrialNotConverted: its trial ended and its customer didn't subscribe within the grace period.merchantClosedCancelledMerchantClosed: its merchant's space closed (a member removed, the organization closed).inactiveOnChainCancelledInactiveOnChain: the contract no longer had it active (cancelled outside QBitFlow).
cancellableAtstringOptionalMinimumCancellationDate (cancellableAt) is the earliest its customer may cancel it (the checkout's minPeriods). Absent when they may cancel it any time
metadatamapOptionalMetadata is the merchant's own key-value data: its checkout session's, copied when the subscription was created, then changed with PATCH /subscriptions/:uuid (merged). {} when none. Only for the merchant (API and webhooks)
Only for the resource's owner
managementPageLinkstringOptionalManagementPageLink is the subscription's page for its customer: where they top up its allowance, raise its maximum, pause, resume or cancel it, and confirm a trial. Share it with them (an email, the merchant's account page)
testbooleanWhether this is test data (testnets) or live data
userUuidstringOptionalMember the resource belongs to; omitted for the organization's own resources
Only for the resource's owner
customerUuidstringOptionalThe customer, when QBitFlow has one (named by the checkout, found by its customerReference, or recorded from the details the customer gave); omitted otherwise
Only for the resource's owner
customerReferencestringOptionalThe merchant's reference of the customer, as the checkout was given it (customerReference), whether or not QBitFlow has a customer with it; omitted otherwise
Only for the resource's owner
customerSummaryOptionalThe customer, named (merchant only, API reads): who paid, even once deleted. Omitted without one
Only for the resource's owner
6 fields of
SummaryuuidstringThe customer (GET /customers/:uuid)
firstNamestringFirst name
lastNamestringOptionalLast name, when given
emailstringEmail, lowercase
referencestringOptionalThe merchant's reference of the customer, when it has one
deletedbooleanThe merchant deleted the customer: it still names its rows, but GET /customers/:uuid answers 404
subscription.created: example body
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "accessUntil": "2026-10-31T13:00:00Z", "allowance": { "amount": "110", "amountMinUnits": "110000000", "amountUsd": "110", "maxPerPeriod": "30", "maxPerPeriodMinUnits": "30000000", "remainingPeriods": 11 }, "createdAt": "2026-10-01T12:00:00Z", "currency": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false }, "currentPeriodEnd": "2026-10-31T12:00:00Z", "customerReference": "crm-5521", "customerUuid": "01a05cd7-2a00-7d00-8000-0000000000d1", "fees": [], "frequency": { "unit": "months", "value": 1 }, "from": "0xabcdef1234567890abcdef1234567890abcdef12", "lastBilledAt": "2026-10-01T12:00:00Z", "managementPageLink": "https://qbitflow.app/user/019cadfd-8900-7a00-8000-0000000000a1/manage?uuid=sub@01a0f755-c320-7000-8000-000000000002", "metadata": { "crm:account": "A-1042", "plan": "pro" }, "nextBillingAt": "2026-10-31T12:00:00Z", "priceUsd": "10", "productUuid": "019cadfd-8900-7c00-8000-0000000000c1", "reference": "customer-7731", "status": "active", "subscriptionHash": "0x2222222222222222222222222222222222222222222222222222222222222222", "test": false, "to": "0x1234567890abcdef1234567890abcdef12345678", "updatedAt": "2026-10-01T12:00:00Z", "uuid": "sub@01a0f755-c320-7000-8000-000000000002" }, "id": "evt_4fad5fdd-5eed-5d81-a971-ba2ade1e6c20", "test": false, "type": "subscription.created", "version": "v2"}Show all 55 linesShow less
subscription.billed#
What it carries, and when
SubscriptionBill: `{ "bill": Bill, "subscription": Subscription }`. bill is the bill paid (as GET /: its currency, chain, explorerUrl, settlement, and the period it pays, periodStart → periodEnd); subscription is the subscription as the bill leaves it (subscription.: active, or stopped, paused, cancelled if that happened while the charge was on its way; subscription.: paid until then; subscription., subscription.). A charge already on its way that lands after the subscription was cancelled is recorded and sent too, with subscription. (the subscription stays cancelled: no period is granted; refund it if your policy says so)
What to do · Renewal
Record the payment (data.bill.uuid sub-hist@…, data., data., data.). Access is paid until data., unless data. is cancelled (a charge that landed just after a cancellation: record it, grant nothing, refund if your policy says so).
2 fields of data (SubscriptionBill)
billBillThe bill paid
30 fields of
Billuuidstringallow read and create, (generated on creation of the payment link)
A prefixed id (pay@…, sub@…)
createdAtstringfromstringtostringamountstringThe amount in the currency's units, exact, a decimal string ("10" for 10 USDC): amountMinUnits / 10^decimals
amountMinUnitsstringThe amount in the currency's smallest unit, a string of digits ("10000000" for 10 USDC, 6 decimals)
amountUsdstringWhat the customer paid, in USD, a decimal string: the price (and, on a one-time payment, the fees its checkout added: fees), which the contracts split between QBitFlow's fee (the referrer's share included), the organization's fee and the merchant (settlement.split). The network fee a customer pays on top (settlement.split.networkFee) is not in it
currencyIdintegerThe currency paid in, written as the currency object under currency (GET /currencies/:id)
txHashstringHash of the transaction in the blockchain
chainstringOptionalThe chain it was paid on: ETH (Ethereum), BASE (Base) or SOL (Solana), the testnet's for a test-mode transfer (currency.test)
explorerUrlstringOptionalIts transaction on the chain's block explorer (the testnet's in test mode). Omitted without a txHash
testbooleanWhether this is test data (testnets) or live data
userUuidstringOptionalMember the resource belongs to; omitted for the organization's own resources
Only for the resource's owner
productNamestringWhat was paid for: the subscription's product name when the bill was paid (the first bill: as it was when the checkout was created). Later product edits don't change it
productDescriptionstringSame, for the product's description. Absent without one
productUuidstringOptionalThe subscription's product
subscriptionUuidstringThe subscription it bills (sub@…)
customerUuidstringOptionalThe customer, when QBitFlow has one (named by the checkout, found by its customerReference, or recorded from the details the customer gave); omitted otherwise
Only for the resource's owner
customerReferencestringOptionalThe merchant's reference of the customer, as the checkout was given it (customerReference), whether or not QBitFlow has a customer with it; omitted otherwise
Only for the resource's owner
customerSummaryOptionalThe customer, named (merchant only, API reads): who paid, even once deleted. Omitted without one
Only for the resource's owner
6 fields of
SummaryuuidstringThe customer (GET /customers/:uuid)
firstNamestringFirst name
lastNamestringOptionalLast name, when given
emailstringEmail, lowercase
referencestringOptionalThe merchant's reference of the customer, when it has one
deletedbooleanThe merchant deleted the customer: it still names its rows, but GET /customers/:uuid answers 404
settlementSettlementOptionalHow it settled: its fees, its split and its transaction (only for the merchant)
Only for the resource's owner
5 fields of
SettlementfeePercentnumberQBitFlow's fee on this payment, in bps (1 % = 100 bps), deducted from the amount paid. Written as feePercent, a percent string
organizationFeeOrganizationFeeOptionalThe organization's fee on its member's payment (a marketplace's commission), paid to the organization's wallet. It is taken from what remains after QBitFlow's fee (contracts SPEC §4.1): organization = (amount - QBitFlow's fee) × its rate, and the member receives the rest. While the organization holds the member's funds, it is withheld from what the organization owes them instead of paid on-chain. Absent: none
2 fields of
OrganizationFeeaddressstringOptionalThe organization's wallet that receives the fee (only for the merchant)
Only for the resource's owner
feePercentnumberThe organization's fee, in bps, taken from what remains after the platform fee. Written as feePercent, a percent string
referralFeeReferralFeeOptionalThe referrer's share of QBitFlow's fee. Absent: none
2 fields of
ReferralFeeaddressstringOptionalThe referrer's wallet that receives the fee (only for the merchant)
Only for the resource's owner
feePercentnumberThe referrer's share of the platform fee, in bps (2000 = 20 % of it). Written as feePercent, a percent string
splitSplitOptionalHow the contracts split what the payer paid. For a payment the organization held for its member, the organization's fee was withheld from the member's share (not paid on-chain): the split is the one a direct payment would have had. Absent on records made before the split was recorded
5 fields of
SplitplatformTokenAmountQBitFlow's fee (the referrer's share not included)
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
organizationTokenAmountOptionalThe organization's fee; absent without one
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
referralTokenAmountOptionalThe referrer's share of QBitFlow's fee; absent without one
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
merchantTokenAmountWhat the merchant received (the member, for a member's payment), net of every fee
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
networkFeeTokenAmountOptionalNetworkFee is the network fee the payer paid on top of the amount, in the payment's currency: the gas refund the contracts took in the token (their GasRefundProcessed event), or, for a payment in a chain's native coin, the fee of the transaction the payer's wallet paid (its gas and, on Base, its L1 data fee), bounded by what QBitFlow prepared. Not the transaction's own cost to its sender (transaction.fee). Absent on records made before it was recorded ("0" once recorded as none)
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
transactionSettlementTransactionOptionalThe transaction that paid it. Absent before its confirmation
6 fields of
SettlementTransactionblockNumberstringThe block (Ethereum, Base) or slot (Solana) it landed in, a string of digits
blockTimestringOptionalWhen its block was produced (UTC); when it was recorded if the chain's node didn't give it. Absent only on records made before it was recorded
gasUsedintegerOptionalThe gas it consumed (Ethereum, Base). Absent on Solana
feeTokenAmountWhat the transaction cost its sender, in the chain's native coin (ETH, SOL; its currency is the payment currency's chain): its gas and, on Base, its L1 data fee. amountUsd at nativePriceUsd
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
l1FeeTokenAmountOptionalThe part of fee that paid Base's L1 data fee, in the native coin. Absent off Base, and on transactions recorded before it was
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
nativePriceUsdstringOptionalNativePriceUSD is the USD price of the chain's native coin when the transaction was recorded (what its fee cost in USD). Absent when the price wasn't available then
periodStartstringOptionalThe start of the period the bill paid: its due date (the subscription's start for its first bill). Omitted on bills recorded before it was
periodEndstringOptionalThe end of the period the bill paid: the subscription's next bill. Paid until then. Omitted on bills recorded before it was
confirmedAtstringOptionalWhen the chain confirmed it: its block's time (settlement.transaction.blockTime). Absent only on records made before it was recorded
paidstringOptionalEverything the customer's wallet sent, in the currency's units (a decimal string): the amount plus the network fee paid on top (settlement.split). A refund's base (refundPercent of it). Only for the merchant; absent when the split wasn't recorded
Only for the resource's owner
paidMinUnitsstringOptionalThe same in the currency's smallest unit, a string of digits. Only for the merchant, like paid
Only for the resource's owner
paidUsdstringOptionalThe same in USD, at the payment's time, a decimal string. Only for the merchant, like paid
Only for the resource's owner
refundRefundSummaryOptionalIts refund (one per transaction); omitted without one. The merchant's own refund appears once its transfer confirmed (approved)
Only for the resource's owner
9 fields of
RefundSummaryuuidstringThe refund (refund@…)
statusstringpending, approved or rejected
One of
pendingapprovedrejectedfailedDeclared, never set: an approval that fails leaves the refund pending, to approve again
initiatedBystringcustomer (a request) or merchant
refundPercentnumberThe share of what the customer paid it sends back, a percent string ("100": all of it). Written as refundPercent
amountstringOptionalWhat it sends back, in the currency's units (a decimal string). Absent when its currency isn't known (a refund requested before refunds named it)
amountMinUnitsstringThe same in the currency's smallest unit (a string of digits)
amountUsdstringOptionalThe same in USD, at the payment's time (a decimal string). Absent when unknown (a refund requested before it was recorded)
createdAtstringWhen it was requested or started
respondedAtstringOptionalWhen it was approved (confirmed) or rejected; absent while pending
refundablebooleanOptionalWhether it can be refunded now
Only for the resource's owner
notRefundableReasonstringOptionalWhy not: refundExists or heldFundsReleased. Omitted when refundable
Only for the resource's owner
subscriptionSubscriptionIts subscription once the bill is paid: active (stopped, paused or cancelled if that happened while the charge was on its way), its next bill and its allowance
29 fields of
SubscriptionuuidstringIts id (sub@…): the checkout session's
A prefixed id (pay@…, sub@…)
referencestringOptionalThe merchant's reference for the subscription (an order or customer id), set when creating its checkout. Only for the merchant: never in the public reads by uuid (GET /hosted/transactions/:txUuid)
Only for the resource's owner
createdAtstringupdatedAtstringIts last change (a bill, a status, its metadata)
fromstringThe subscriber's wallet: the one it bills. Absent when unknown (a trial not confirmed yet may have none)
tostringThe wallet its bills pay: the merchant's (or, while the organization holds a member's funds, the organization's)
productUuidstringIts product (GET /products/:uuid). Absent without one
subscriptionHashstringThe hash of the transaction that created the subscription on-chain. Absent until then (a trial, until its customer confirms it)
currencyIdintegerThe currency (token) billed, written as the currency object under currency (GET /currencies/:id)
frequencyDurationBilling interval in seconds, as on-chain (e.g. 2592000 = 30 days). Written as a types.Duration in responses and webhooks (e.g. {"value": 1, "unit": "months"})
2 fields of
DurationvalueintegerNumber of units; 0 means no duration
unitstringRequired when value > 0. "months" = 30 days, "years" = 365 days
oneof=seconds minutes hours days weeks months years
statusstringSubscriptionStatus (status) is where it stands: trial, trialExpired, active, pastDue, paused, stopped or cancelled. Every change sends subscription.statusChanged. Don't grant access from it: grant access while now < accessUntil (docs/api/guides/subscriptions.md § Access)
One of:
trialStatusTrial is a free trial: nothing was signed on-chain yet.trialExpiredStatusTrialExpired is a trial that ended without its customer confirming it (actionRequired confirmTrial): cancelled after the grace period unless they subscribe.activeStatusActive is a subscription billed on its due dates. Its customer may have something to do (actionRequired: topUpAllowance, raiseMaximum) while it stays active.pastDueStatusPastDue indicates a subscription whose last bill failed: its retries run.pausedStatusPaused is a subscription its customer paused (a message signed with its wallet): QBitFlow doesn't bill it until they resume it. On-chain it is still active. It keeps the period it paid for (currentPeriodEnd).stoppedStatusStopped is a subscription cancelled during its billing period (by its customer, or its merchant's force cancel with immediate=false): never billed again, it still runs until its paid period ends (nextBillingDate), then it is cancelled.cancelledStatusCancelled indicates a cancelled subscription.
actionRequiredstringOptionalActionRequired is what its customer must do, if anything (absent: nothing): topUpAllowance (the allowance covers about one more bill at most), raiseMaximum (a bill waits for a new maximum per period), confirmTrial (the trial ended unconfirmed). Every change (set, changed or cleared) sends subscription.actionRequiredChanged
One of
topUpAllowanceActionTopUpAllowance: what remains of the allowance covers about one more bill at most (or none). The customer adds periods from their management page.raiseMaximumActionRaiseMaximum: a bill is above the maximum per period the customer signed (the token's price moved); it waits up to 7 days for a new maximum, signed from the management page.confirmTrialActionConfirmTrial: the trial ended unconfirmed (trialExpired); the customer subscribes from their management page before the grace period ends.
currentPeriodEndstringOptionalCurrentPeriodEnd is the paid-through date. A trial: its end. active, paused, stopped: the end of the period paid for (the next bill's due date). pastDue: the start of the unpaid period (past). cancelled: when it was cancelled, or the end of its last paid period if that came first. For access, read accessUntil: an active subscription is renewed a few minutes after this date
accessUntilstringOptionalAccessUntil is when access ends: grant it while now < accessUntil, whatever the status. An active subscription: currentPeriodEnd plus 1 hour, since its renewal is billed within minutes of its due date (a subscriber whose bill is on its way keeps access; a failed bill sets pastDue). Every other status: currentPeriodEnd
priceUsdstringPriceUSD is the price per period the customer subscribed at, in USD (a decimal string, e.g. "9.99"), locked at signup (a free trial's too): every bill charges it and its lines (fees), converted to the token at the bill's price. A later change of the product's price applies to new subscribers only. Never "0"
feesarray of FeeLineFees is what its checkout added to the price, line by line, as the customer saw them (a tax, shipping, the processing fee when the customer pays it), locked at signup with the price: every bill charges priceUsd plus these lines. [] without any
4 fields of each
FeeLinetypestringcustom (the merchant's line) or processingFee (QBitFlow's fee, which the merchant has the customer pay)
One of:
customThe merchant's own line: a tax, shipping, a service feeprocessingFeeQBitFlow's fee, which the merchant has the customer pay (computed by QBitFlow)
labelstringThe line's name, as the checkout shows it ("Processing fee" for the processing fee)
descriptionstringOptionalOptional: more about the line, as the merchant wrote it. Omitted without one
amountUsdstringThe line's amount in USD, a decimal string with at most 2 decimals
allowanceAllowanceOptionalAllowanceStatus (allowance) is what the subscriber's wallet still lets QBitFlow bill, and the most one bill may charge, at today's price. Absent during a free trial (nothing signed yet)
6 fields of
AllowanceamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
remainingPeriodsintegerOptionalRemainingPeriods is how many whole bills the allowance still covers at today's price: the allowance divided by a bill (priceUsd and its fees, converted to the token), rounded down, before the network fee each bill adds. 0 is a value: the customer must top it up (actionRequired topUpAllowance) before the next bill. Absent without a price
maxPerPeriodstringOptionalMaxPerPeriod is the most one bill may charge, as the subscriber signed it, in the token's units (maxPerPeriodMinUnits: its on-chain integer): a bill above it waits for a new maximum (actionRequired raiseMaximum). Absent when the subscription has none recorded
maxPerPeriodMinUnitsstringOptional
lastBilledAtstringOptionalWhen it was last billed. Absent before its first bill (a trial)
nextBillingAtstringOptionalWhen its next bill is due (a trial: when it ends; stopped: when it is cancelled). Absent once cancelled
cancelledAtstringOptionalWhen it was cancelled (absent until then, and for subscriptions cancelled before it was recorded)
cancellationReasonstringOptionalWhy it stopped or was cancelled: customer, merchant, billingFailed, trialNotConverted, merchantClosed or inactiveOnChain. Absent while it runs (and for subscriptions cancelled before it was recorded)
One of:
customerCancelledByCustomer: its customer cancelled it (it was stopped until its period ended).merchantCancelledByMerchant: its merchant cancelled it (POST /subscriptions/:uuid/cancel).billingFailedCancelledBillingFailed: its bill's last attempt failed.trialNotConvertedCancelledTrialNotConverted: its trial ended and its customer didn't subscribe within the grace period.merchantClosedCancelledMerchantClosed: its merchant's space closed (a member removed, the organization closed).inactiveOnChainCancelledInactiveOnChain: the contract no longer had it active (cancelled outside QBitFlow).
cancellableAtstringOptionalMinimumCancellationDate (cancellableAt) is the earliest its customer may cancel it (the checkout's minPeriods). Absent when they may cancel it any time
metadatamapOptionalMetadata is the merchant's own key-value data: its checkout session's, copied when the subscription was created, then changed with PATCH /subscriptions/:uuid (merged). {} when none. Only for the merchant (API and webhooks)
Only for the resource's owner
managementPageLinkstringOptionalManagementPageLink is the subscription's page for its customer: where they top up its allowance, raise its maximum, pause, resume or cancel it, and confirm a trial. Share it with them (an email, the merchant's account page)
testbooleanWhether this is test data (testnets) or live data
userUuidstringOptionalMember the resource belongs to; omitted for the organization's own resources
Only for the resource's owner
customerUuidstringOptionalThe customer, when QBitFlow has one (named by the checkout, found by its customerReference, or recorded from the details the customer gave); omitted otherwise
Only for the resource's owner
customerReferencestringOptionalThe merchant's reference of the customer, as the checkout was given it (customerReference), whether or not QBitFlow has a customer with it; omitted otherwise
Only for the resource's owner
customerSummaryOptionalThe customer, named (merchant only, API reads): who paid, even once deleted. Omitted without one
Only for the resource's owner
6 fields of
SummaryuuidstringThe customer (GET /customers/:uuid)
firstNamestringFirst name
lastNamestringOptionalLast name, when given
emailstringEmail, lowercase
referencestringOptionalThe merchant's reference of the customer, when it has one
deletedbooleanThe merchant deleted the customer: it still names its rows, but GET /customers/:uuid answers 404
subscription.billed: example body
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "bill": { "amount": "10", "amountMinUnits": "10000000", "amountUsd": "10", "chain": "BASE", "confirmedAt": "2026-10-01T11:59:58Z", "createdAt": "2026-10-01T12:00:00Z", "currency": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false }, "customerReference": "crm-5521", "customerUuid": "01a05cd7-2a00-7d00-8000-0000000000d1", "explorerUrl": "https://basescan.org/tx/0x3333333333333333333333333333333333333333333333333333333333333333", "from": "0xabcdef1234567890abcdef1234567890abcdef12", "paid": "10.0042", "paidMinUnits": "10004200", "paidUsd": "10.0042", "periodEnd": "2026-10-31T12:00:00Z", "periodStart": "2026-10-01T12:00:00Z", "productDescription": "Monthly access to the Pro plan", "productName": "Pro plan", "productUuid": "019cadfd-8900-7c00-8000-0000000000c1", "settlement": { "feePercent": "0.99", "split": { "merchant": { "amount": "9.901", "amountMinUnits": "9901000", "amountUsd": "9.901" }, "networkFee": { "amount": "0.0042", "amountMinUnits": "4200", "amountUsd": "0.0042" }, "platform": { "amount": "0.099", "amountMinUnits": "99000", "amountUsd": "0.099" } }, "transaction": { "blockNumber": "52468103", "blockTime": "2026-10-01T11:59:58Z", "fee": { "amount": "0.00000114", "amountMinUnits": "1140000000000", "amountUsd": "0.005144" }, "gasUsed": 95000, "nativePriceUsd": "4512.37" } }, "subscriptionUuid": "sub@01a0f755-c320-7000-8000-000000000002", "test": false, "to": "0x1234567890abcdef1234567890abcdef12345678", "txHash": "0x3333333333333333333333333333333333333333333333333333333333333333", "uuid": "sub-hist@01a0f755-f200-7000-8000-000000000005" }, "subscription": { "accessUntil": "2026-10-31T13:00:00Z", "allowance": { "amount": "110", "amountMinUnits": "110000000", "amountUsd": "110", "maxPerPeriod": "30", "maxPerPeriodMinUnits": "30000000", "remainingPeriods": 11 }, "createdAt": "2026-10-01T12:00:00Z", "currency": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false }, "currentPeriodEnd": "2026-10-31T12:00:00Z", "customerReference": "crm-5521", "customerUuid": "01a05cd7-2a00-7d00-8000-0000000000d1", "fees": [], "frequency": { "unit": "months", "value": 1 }, "from": "0xabcdef1234567890abcdef1234567890abcdef12", "lastBilledAt": "2026-10-01T12:00:00Z", "managementPageLink": "https://qbitflow.app/user/019cadfd-8900-7a00-8000-0000000000a1/manage?uuid=sub@01a0f755-c320-7000-8000-000000000002", "metadata": { "crm:account": "A-1042", "plan": "pro" }, "nextBillingAt": "2026-10-31T12:00:00Z", "priceUsd": "10", "productUuid": "019cadfd-8900-7c00-8000-0000000000c1", "reference": "customer-7731", "status": "active", "subscriptionHash": "0x2222222222222222222222222222222222222222222222222222222222222222", "test": false, "to": "0x1234567890abcdef1234567890abcdef12345678", "updatedAt": "2026-10-01T12:00:00Z", "uuid": "sub@01a0f755-c320-7000-8000-000000000002" } }, "id": "evt_bca1239f-e7d7-517c-b004-08aaeee1e76f", "test": false, "type": "subscription.billed", "version": "v2"}Show all 124 linesShow less
subscription.statusChanged#
What it carries, and when
SubscriptionStatusChange: the Subscription with its new status, and previousStatus. Every status change sends it, once (its id derives from what caused it and from the change's place in the subscription's history: a subscription cancelled, revived and cancelled again sends two → cancelled events, with two ids). A customer's cancellation: active or paused → stopped when it is confirmed, then stopped → cancelled at the end of the paid period (nextBillingAt); a merchant's cancellation (POST /): → cancelled at once, or → stopped then → cancelled with immediate=false; the last failed bill, a trial not confirmed in its grace period, a closed merchant: → cancelled. stopped and cancelled carry cancellationReason (customer, merchant, billingFailed, trialNotConverted, merchantClosed, inactiveOnChain; a stopped subscription keeps the reason it stopped for), cancelled also cancelledAt, an allowance of "0" and no nextBillingAt. A free trial confirmed by its customer: trial or trialExpired → active, right before its first subscription. (same batch), once (its id derives from the confirming transaction). A pastDue subscription becomes active when its bill is paid (after that subscription.), never on a top-up alone. The customer pausing or resuming it (a message signed with their wallet): active → paused, paused → active. What the customer must do (actionRequired) is no status: it changes with subscription.
What to do · Status change
Apply your access rule: grant while now < accessUntil, whatever the status (Subscriptions § Access): stopped and paused keep the period paid for; cancelled: the end (cancellationReason says why).
30 fields of data (SubscriptionStatusChange)
uuidstringIts id (sub@…): the checkout session's
A prefixed id (pay@…, sub@…)
referencestringOptionalThe merchant's reference for the subscription (an order or customer id), set when creating its checkout. Only for the merchant: never in the public reads by uuid (GET /hosted/transactions/:txUuid)
Only for the resource's owner
createdAtstringupdatedAtstringIts last change (a bill, a status, its metadata)
fromstringThe subscriber's wallet: the one it bills. Absent when unknown (a trial not confirmed yet may have none)
tostringThe wallet its bills pay: the merchant's (or, while the organization holds a member's funds, the organization's)
productUuidstringIts product (GET /products/:uuid). Absent without one
subscriptionHashstringThe hash of the transaction that created the subscription on-chain. Absent until then (a trial, until its customer confirms it)
currencyIdintegerThe currency (token) billed, written as the currency object under currency (GET /currencies/:id)
frequencyDurationBilling interval in seconds, as on-chain (e.g. 2592000 = 30 days). Written as a types.Duration in responses and webhooks (e.g. {"value": 1, "unit": "months"})
2 fields of
DurationvalueintegerNumber of units; 0 means no duration
unitstringRequired when value > 0. "months" = 30 days, "years" = 365 days
oneof=seconds minutes hours days weeks months years
statusstringSubscriptionStatus (status) is where it stands: trial, trialExpired, active, pastDue, paused, stopped or cancelled. Every change sends subscription.statusChanged. Don't grant access from it: grant access while now < accessUntil (docs/api/guides/subscriptions.md § Access)
One of:
trialStatusTrial is a free trial: nothing was signed on-chain yet.trialExpiredStatusTrialExpired is a trial that ended without its customer confirming it (actionRequired confirmTrial): cancelled after the grace period unless they subscribe.activeStatusActive is a subscription billed on its due dates. Its customer may have something to do (actionRequired: topUpAllowance, raiseMaximum) while it stays active.pastDueStatusPastDue indicates a subscription whose last bill failed: its retries run.pausedStatusPaused is a subscription its customer paused (a message signed with its wallet): QBitFlow doesn't bill it until they resume it. On-chain it is still active. It keeps the period it paid for (currentPeriodEnd).stoppedStatusStopped is a subscription cancelled during its billing period (by its customer, or its merchant's force cancel with immediate=false): never billed again, it still runs until its paid period ends (nextBillingDate), then it is cancelled.cancelledStatusCancelled indicates a cancelled subscription.
actionRequiredstringOptionalActionRequired is what its customer must do, if anything (absent: nothing): topUpAllowance (the allowance covers about one more bill at most), raiseMaximum (a bill waits for a new maximum per period), confirmTrial (the trial ended unconfirmed). Every change (set, changed or cleared) sends subscription.actionRequiredChanged
One of
topUpAllowanceActionTopUpAllowance: what remains of the allowance covers about one more bill at most (or none). The customer adds periods from their management page.raiseMaximumActionRaiseMaximum: a bill is above the maximum per period the customer signed (the token's price moved); it waits up to 7 days for a new maximum, signed from the management page.confirmTrialActionConfirmTrial: the trial ended unconfirmed (trialExpired); the customer subscribes from their management page before the grace period ends.
currentPeriodEndstringOptionalCurrentPeriodEnd is the paid-through date. A trial: its end. active, paused, stopped: the end of the period paid for (the next bill's due date). pastDue: the start of the unpaid period (past). cancelled: when it was cancelled, or the end of its last paid period if that came first. For access, read accessUntil: an active subscription is renewed a few minutes after this date
accessUntilstringOptionalAccessUntil is when access ends: grant it while now < accessUntil, whatever the status. An active subscription: currentPeriodEnd plus 1 hour, since its renewal is billed within minutes of its due date (a subscriber whose bill is on its way keeps access; a failed bill sets pastDue). Every other status: currentPeriodEnd
priceUsdstringPriceUSD is the price per period the customer subscribed at, in USD (a decimal string, e.g. "9.99"), locked at signup (a free trial's too): every bill charges it and its lines (fees), converted to the token at the bill's price. A later change of the product's price applies to new subscribers only. Never "0"
feesarray of FeeLineFees is what its checkout added to the price, line by line, as the customer saw them (a tax, shipping, the processing fee when the customer pays it), locked at signup with the price: every bill charges priceUsd plus these lines. [] without any
4 fields of each
FeeLinetypestringcustom (the merchant's line) or processingFee (QBitFlow's fee, which the merchant has the customer pay)
One of:
customThe merchant's own line: a tax, shipping, a service feeprocessingFeeQBitFlow's fee, which the merchant has the customer pay (computed by QBitFlow)
labelstringThe line's name, as the checkout shows it ("Processing fee" for the processing fee)
descriptionstringOptionalOptional: more about the line, as the merchant wrote it. Omitted without one
amountUsdstringThe line's amount in USD, a decimal string with at most 2 decimals
allowanceAllowanceOptionalAllowanceStatus (allowance) is what the subscriber's wallet still lets QBitFlow bill, and the most one bill may charge, at today's price. Absent during a free trial (nothing signed yet)
6 fields of
AllowanceamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
remainingPeriodsintegerOptionalRemainingPeriods is how many whole bills the allowance still covers at today's price: the allowance divided by a bill (priceUsd and its fees, converted to the token), rounded down, before the network fee each bill adds. 0 is a value: the customer must top it up (actionRequired topUpAllowance) before the next bill. Absent without a price
maxPerPeriodstringOptionalMaxPerPeriod is the most one bill may charge, as the subscriber signed it, in the token's units (maxPerPeriodMinUnits: its on-chain integer): a bill above it waits for a new maximum (actionRequired raiseMaximum). Absent when the subscription has none recorded
maxPerPeriodMinUnitsstringOptional
lastBilledAtstringOptionalWhen it was last billed. Absent before its first bill (a trial)
nextBillingAtstringOptionalWhen its next bill is due (a trial: when it ends; stopped: when it is cancelled). Absent once cancelled
cancelledAtstringOptionalWhen it was cancelled (absent until then, and for subscriptions cancelled before it was recorded)
cancellationReasonstringOptionalWhy it stopped or was cancelled: customer, merchant, billingFailed, trialNotConverted, merchantClosed or inactiveOnChain. Absent while it runs (and for subscriptions cancelled before it was recorded)
One of:
customerCancelledByCustomer: its customer cancelled it (it was stopped until its period ended).merchantCancelledByMerchant: its merchant cancelled it (POST /subscriptions/:uuid/cancel).billingFailedCancelledBillingFailed: its bill's last attempt failed.trialNotConvertedCancelledTrialNotConverted: its trial ended and its customer didn't subscribe within the grace period.merchantClosedCancelledMerchantClosed: its merchant's space closed (a member removed, the organization closed).inactiveOnChainCancelledInactiveOnChain: the contract no longer had it active (cancelled outside QBitFlow).
cancellableAtstringOptionalMinimumCancellationDate (cancellableAt) is the earliest its customer may cancel it (the checkout's minPeriods). Absent when they may cancel it any time
metadatamapOptionalMetadata is the merchant's own key-value data: its checkout session's, copied when the subscription was created, then changed with PATCH /subscriptions/:uuid (merged). {} when none. Only for the merchant (API and webhooks)
Only for the resource's owner
managementPageLinkstringOptionalManagementPageLink is the subscription's page for its customer: where they top up its allowance, raise its maximum, pause, resume or cancel it, and confirm a trial. Share it with them (an email, the merchant's account page)
testbooleanWhether this is test data (testnets) or live data
userUuidstringOptionalMember the resource belongs to; omitted for the organization's own resources
Only for the resource's owner
customerUuidstringOptionalThe customer, when QBitFlow has one (named by the checkout, found by its customerReference, or recorded from the details the customer gave); omitted otherwise
Only for the resource's owner
customerReferencestringOptionalThe merchant's reference of the customer, as the checkout was given it (customerReference), whether or not QBitFlow has a customer with it; omitted otherwise
Only for the resource's owner
customerSummaryOptionalThe customer, named (merchant only, API reads): who paid, even once deleted. Omitted without one
Only for the resource's owner
6 fields of
SummaryuuidstringThe customer (GET /customers/:uuid)
firstNamestringFirst name
lastNamestringOptionalLast name, when given
emailstringEmail, lowercase
referencestringOptionalThe merchant's reference of the customer, when it has one
deletedbooleanThe merchant deleted the customer: it still names its rows, but GET /customers/:uuid answers 404
previousStatusstringOne of:
trialStatusTrial is a free trial: nothing was signed on-chain yet.trialExpiredStatusTrialExpired is a trial that ended without its customer confirming it (actionRequired confirmTrial): cancelled after the grace period unless they subscribe.activeStatusActive is a subscription billed on its due dates. Its customer may have something to do (actionRequired: topUpAllowance, raiseMaximum) while it stays active.pastDueStatusPastDue indicates a subscription whose last bill failed: its retries run.pausedStatusPaused is a subscription its customer paused (a message signed with its wallet): QBitFlow doesn't bill it until they resume it. On-chain it is still active. It keeps the period it paid for (currentPeriodEnd).stoppedStatusStopped is a subscription cancelled during its billing period (by its customer, or its merchant's force cancel with immediate=false): never billed again, it still runs until its paid period ends (nextBillingDate), then it is cancelled.cancelledStatusCancelled indicates a cancelled subscription.
subscription.statusChanged: example body
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "accessUntil": "2026-10-31T13:00:00Z", "allowance": { "amount": "110", "amountMinUnits": "110000000", "amountUsd": "110", "maxPerPeriod": "30", "maxPerPeriodMinUnits": "30000000", "remainingPeriods": 11 }, "createdAt": "2026-09-01T12:00:00Z", "currency": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false }, "currentPeriodEnd": "2026-10-31T12:00:00Z", "customerReference": "crm-5521", "customerUuid": "01a05cd7-2a00-7d00-8000-0000000000d1", "fees": [], "frequency": { "unit": "months", "value": 1 }, "from": "0xabcdef1234567890abcdef1234567890abcdef12", "lastBilledAt": "2026-10-01T12:00:00Z", "managementPageLink": "https://qbitflow.app/user/019cadfd-8900-7a00-8000-0000000000a1/manage?uuid=sub@01a05cd7-2a00-7000-8000-000000000003", "metadata": { "crm:account": "A-1042", "plan": "pro" }, "nextBillingAt": "2026-10-31T12:00:00Z", "previousStatus": "pastDue", "priceUsd": "10", "productUuid": "019cadfd-8900-7c00-8000-0000000000c1", "reference": "customer-7731", "status": "active", "subscriptionHash": "0x2222222222222222222222222222222222222222222222222222222222222222", "test": false, "to": "0x1234567890abcdef1234567890abcdef12345678", "updatedAt": "2026-10-01T12:00:00Z", "uuid": "sub@01a05cd7-2a00-7000-8000-000000000003" }, "id": "evt_9c02c80f-4040-582c-a749-f50329b95421", "test": false, "type": "subscription.statusChanged", "version": "v2"}Show all 56 linesShow less
subscription.actionRequiredChanged#
What it carries, and when
SubscriptionActionChange: the Subscription with its new actionRequired (topUpAllowance, raiseMaximum, confirmTrial; absent: nothing to do anymore), and previousActionRequired (absent: nothing before). The subscription's managementPageLink is where the customer acts. Sent once per change (set, changed or cleared), whether or not the status changes too (then right after its subscription.): topUpAllowance when a paid bill leaves the allowance at about one more bill or less, cleared by a top-up; raiseMaximum when a bill above the customer's maximum starts waiting for a new one (the status kept), cleared when a bill is paid; confirmTrial with the trial's trialExpired, cleared when it is confirmed. Any action is cleared when the subscription stops or is cancelled. Not sent at creation (subscription. carries it)
What to do · Customer action
Ask the customer to act when actionRequired is set (topUpAllowance, raiseMaximum, confirmTrial: managementPageLink is where); absent: nothing left to do (previousActionRequired says what was done).
30 fields of data (SubscriptionActionChange)
uuidstringIts id (sub@…): the checkout session's
A prefixed id (pay@…, sub@…)
referencestringOptionalThe merchant's reference for the subscription (an order or customer id), set when creating its checkout. Only for the merchant: never in the public reads by uuid (GET /hosted/transactions/:txUuid)
Only for the resource's owner
createdAtstringupdatedAtstringIts last change (a bill, a status, its metadata)
fromstringThe subscriber's wallet: the one it bills. Absent when unknown (a trial not confirmed yet may have none)
tostringThe wallet its bills pay: the merchant's (or, while the organization holds a member's funds, the organization's)
productUuidstringIts product (GET /products/:uuid). Absent without one
subscriptionHashstringThe hash of the transaction that created the subscription on-chain. Absent until then (a trial, until its customer confirms it)
currencyIdintegerThe currency (token) billed, written as the currency object under currency (GET /currencies/:id)
frequencyDurationBilling interval in seconds, as on-chain (e.g. 2592000 = 30 days). Written as a types.Duration in responses and webhooks (e.g. {"value": 1, "unit": "months"})
2 fields of
DurationvalueintegerNumber of units; 0 means no duration
unitstringRequired when value > 0. "months" = 30 days, "years" = 365 days
oneof=seconds minutes hours days weeks months years
statusstringSubscriptionStatus (status) is where it stands: trial, trialExpired, active, pastDue, paused, stopped or cancelled. Every change sends subscription.statusChanged. Don't grant access from it: grant access while now < accessUntil (docs/api/guides/subscriptions.md § Access)
One of:
trialStatusTrial is a free trial: nothing was signed on-chain yet.trialExpiredStatusTrialExpired is a trial that ended without its customer confirming it (actionRequired confirmTrial): cancelled after the grace period unless they subscribe.activeStatusActive is a subscription billed on its due dates. Its customer may have something to do (actionRequired: topUpAllowance, raiseMaximum) while it stays active.pastDueStatusPastDue indicates a subscription whose last bill failed: its retries run.pausedStatusPaused is a subscription its customer paused (a message signed with its wallet): QBitFlow doesn't bill it until they resume it. On-chain it is still active. It keeps the period it paid for (currentPeriodEnd).stoppedStatusStopped is a subscription cancelled during its billing period (by its customer, or its merchant's force cancel with immediate=false): never billed again, it still runs until its paid period ends (nextBillingDate), then it is cancelled.cancelledStatusCancelled indicates a cancelled subscription.
actionRequiredstringOptionalActionRequired is what its customer must do, if anything (absent: nothing): topUpAllowance (the allowance covers about one more bill at most), raiseMaximum (a bill waits for a new maximum per period), confirmTrial (the trial ended unconfirmed). Every change (set, changed or cleared) sends subscription.actionRequiredChanged
One of
topUpAllowanceActionTopUpAllowance: what remains of the allowance covers about one more bill at most (or none). The customer adds periods from their management page.raiseMaximumActionRaiseMaximum: a bill is above the maximum per period the customer signed (the token's price moved); it waits up to 7 days for a new maximum, signed from the management page.confirmTrialActionConfirmTrial: the trial ended unconfirmed (trialExpired); the customer subscribes from their management page before the grace period ends.
currentPeriodEndstringOptionalCurrentPeriodEnd is the paid-through date. A trial: its end. active, paused, stopped: the end of the period paid for (the next bill's due date). pastDue: the start of the unpaid period (past). cancelled: when it was cancelled, or the end of its last paid period if that came first. For access, read accessUntil: an active subscription is renewed a few minutes after this date
accessUntilstringOptionalAccessUntil is when access ends: grant it while now < accessUntil, whatever the status. An active subscription: currentPeriodEnd plus 1 hour, since its renewal is billed within minutes of its due date (a subscriber whose bill is on its way keeps access; a failed bill sets pastDue). Every other status: currentPeriodEnd
priceUsdstringPriceUSD is the price per period the customer subscribed at, in USD (a decimal string, e.g. "9.99"), locked at signup (a free trial's too): every bill charges it and its lines (fees), converted to the token at the bill's price. A later change of the product's price applies to new subscribers only. Never "0"
feesarray of FeeLineFees is what its checkout added to the price, line by line, as the customer saw them (a tax, shipping, the processing fee when the customer pays it), locked at signup with the price: every bill charges priceUsd plus these lines. [] without any
4 fields of each
FeeLinetypestringcustom (the merchant's line) or processingFee (QBitFlow's fee, which the merchant has the customer pay)
One of:
customThe merchant's own line: a tax, shipping, a service feeprocessingFeeQBitFlow's fee, which the merchant has the customer pay (computed by QBitFlow)
labelstringThe line's name, as the checkout shows it ("Processing fee" for the processing fee)
descriptionstringOptionalOptional: more about the line, as the merchant wrote it. Omitted without one
amountUsdstringThe line's amount in USD, a decimal string with at most 2 decimals
allowanceAllowanceOptionalAllowanceStatus (allowance) is what the subscriber's wallet still lets QBitFlow bill, and the most one bill may charge, at today's price. Absent during a free trial (nothing signed yet)
6 fields of
AllowanceamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
remainingPeriodsintegerOptionalRemainingPeriods is how many whole bills the allowance still covers at today's price: the allowance divided by a bill (priceUsd and its fees, converted to the token), rounded down, before the network fee each bill adds. 0 is a value: the customer must top it up (actionRequired topUpAllowance) before the next bill. Absent without a price
maxPerPeriodstringOptionalMaxPerPeriod is the most one bill may charge, as the subscriber signed it, in the token's units (maxPerPeriodMinUnits: its on-chain integer): a bill above it waits for a new maximum (actionRequired raiseMaximum). Absent when the subscription has none recorded
maxPerPeriodMinUnitsstringOptional
lastBilledAtstringOptionalWhen it was last billed. Absent before its first bill (a trial)
nextBillingAtstringOptionalWhen its next bill is due (a trial: when it ends; stopped: when it is cancelled). Absent once cancelled
cancelledAtstringOptionalWhen it was cancelled (absent until then, and for subscriptions cancelled before it was recorded)
cancellationReasonstringOptionalWhy it stopped or was cancelled: customer, merchant, billingFailed, trialNotConverted, merchantClosed or inactiveOnChain. Absent while it runs (and for subscriptions cancelled before it was recorded)
One of:
customerCancelledByCustomer: its customer cancelled it (it was stopped until its period ended).merchantCancelledByMerchant: its merchant cancelled it (POST /subscriptions/:uuid/cancel).billingFailedCancelledBillingFailed: its bill's last attempt failed.trialNotConvertedCancelledTrialNotConverted: its trial ended and its customer didn't subscribe within the grace period.merchantClosedCancelledMerchantClosed: its merchant's space closed (a member removed, the organization closed).inactiveOnChainCancelledInactiveOnChain: the contract no longer had it active (cancelled outside QBitFlow).
cancellableAtstringOptionalMinimumCancellationDate (cancellableAt) is the earliest its customer may cancel it (the checkout's minPeriods). Absent when they may cancel it any time
metadatamapOptionalMetadata is the merchant's own key-value data: its checkout session's, copied when the subscription was created, then changed with PATCH /subscriptions/:uuid (merged). {} when none. Only for the merchant (API and webhooks)
Only for the resource's owner
managementPageLinkstringOptionalManagementPageLink is the subscription's page for its customer: where they top up its allowance, raise its maximum, pause, resume or cancel it, and confirm a trial. Share it with them (an email, the merchant's account page)
testbooleanWhether this is test data (testnets) or live data
userUuidstringOptionalMember the resource belongs to; omitted for the organization's own resources
Only for the resource's owner
customerUuidstringOptionalThe customer, when QBitFlow has one (named by the checkout, found by its customerReference, or recorded from the details the customer gave); omitted otherwise
Only for the resource's owner
customerReferencestringOptionalThe merchant's reference of the customer, as the checkout was given it (customerReference), whether or not QBitFlow has a customer with it; omitted otherwise
Only for the resource's owner
customerSummaryOptionalThe customer, named (merchant only, API reads): who paid, even once deleted. Omitted without one
Only for the resource's owner
6 fields of
SummaryuuidstringThe customer (GET /customers/:uuid)
firstNamestringFirst name
lastNamestringOptionalLast name, when given
emailstringEmail, lowercase
referencestringOptionalThe merchant's reference of the customer, when it has one
deletedbooleanThe merchant deleted the customer: it still names its rows, but GET /customers/:uuid answers 404
previousActionRequiredstringOptionalWhat its customer had to do before (absent: nothing)
One of
topUpAllowanceActionTopUpAllowance: what remains of the allowance covers about one more bill at most (or none). The customer adds periods from their management page.raiseMaximumActionRaiseMaximum: a bill is above the maximum per period the customer signed (the token's price moved); it waits up to 7 days for a new maximum, signed from the management page.confirmTrialActionConfirmTrial: the trial ended unconfirmed (trialExpired); the customer subscribes from their management page before the grace period ends.
subscription.actionRequiredChanged: example body
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "accessUntil": "2026-10-31T13:00:00Z", "actionRequired": "topUpAllowance", "allowance": { "amount": "1.5", "amountMinUnits": "1500000", "amountUsd": "1.5", "maxPerPeriod": "30", "maxPerPeriodMinUnits": "30000000", "remainingPeriods": 0 }, "createdAt": "2026-09-01T12:00:00Z", "currency": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false }, "currentPeriodEnd": "2026-10-31T12:00:00Z", "customerReference": "crm-5521", "customerUuid": "01a05cd7-2a00-7d00-8000-0000000000d1", "fees": [], "frequency": { "unit": "months", "value": 1 }, "from": "0xabcdef1234567890abcdef1234567890abcdef12", "lastBilledAt": "2026-10-01T12:00:00Z", "managementPageLink": "https://qbitflow.app/user/019cadfd-8900-7a00-8000-0000000000a1/manage?uuid=sub@01a05cd7-2a00-7000-8000-000000000003", "metadata": { "crm:account": "A-1042", "plan": "pro" }, "nextBillingAt": "2026-10-31T12:00:00Z", "priceUsd": "10", "productUuid": "019cadfd-8900-7c00-8000-0000000000c1", "reference": "customer-7731", "status": "active", "subscriptionHash": "0x2222222222222222222222222222222222222222222222222222222222222222", "test": false, "to": "0x1234567890abcdef1234567890abcdef12345678", "updatedAt": "2026-10-01T12:00:00Z", "uuid": "sub@01a05cd7-2a00-7000-8000-000000000003" }, "id": "evt_b4e9b659-4641-5b64-bf03-ade86da0c456", "test": false, "type": "subscription.actionRequiredChanged", "version": "v2"}Show all 56 linesShow less
subscription.billingFailed#
What it carries, and when
SubscriptionBillingFailure: the Subscription as the failure leaves it, the bill (billUuid, the sub-hist@… its subscription. will have; amountUsd, a decimal string), reason (what the customer does: insufficientBalance: add funds to the wallet; allowanceExhausted: top up the allowance; approvalRevoked: approve the token again; maxAmountExceeded: sign a new maximum; other: the network refused the charge), failureCode (the charge's error code, errors), nextAttemptAt (when it is tried again at the latest; absent after the last attempt), attempt and remainingAttempts (retried 1, 3, 5 and 7 days after the first failed attempt, at least a day apart, and at once when the customer raises their allowance or maximum: such an extra try sends nothing if it fails; at 0 the subscription is still pastDue here and its cancellation follows: subscription. to cancelled). The subscription's managementPageLink is where the customer fixes it. Sent once per failed attempt (pastDue), and once when a bill above the customer's maximum starts waiting for a new one (maxAmountExceeded: the status is kept, actionRequired becomes raiseMaximum; no attempt spent: attempt stays the count so far). A status or actionRequired change comes after its billingFailed. A subscription read also carries dunning (where the failing bill stands); webhooks don't. QBitFlow's own failures (a network or a price unavailable, its own signature refused) send nothing (Subscriptions and bills § Billing)
What to do · Failing renewal
Tell the customer what to do (reason; data. fixes it: allowance, approval, maximum; or funds). remainingAttempts: 0: its cancellation follows.
36 fields of data (SubscriptionBillingFailure)
uuidstringIts id (sub@…): the checkout session's
A prefixed id (pay@…, sub@…)
referencestringOptionalThe merchant's reference for the subscription (an order or customer id), set when creating its checkout. Only for the merchant: never in the public reads by uuid (GET /hosted/transactions/:txUuid)
Only for the resource's owner
createdAtstringupdatedAtstringIts last change (a bill, a status, its metadata)
fromstringThe subscriber's wallet: the one it bills. Absent when unknown (a trial not confirmed yet may have none)
tostringThe wallet its bills pay: the merchant's (or, while the organization holds a member's funds, the organization's)
productUuidstringIts product (GET /products/:uuid). Absent without one
subscriptionHashstringThe hash of the transaction that created the subscription on-chain. Absent until then (a trial, until its customer confirms it)
currencyIdintegerThe currency (token) billed, written as the currency object under currency (GET /currencies/:id)
frequencyDurationBilling interval in seconds, as on-chain (e.g. 2592000 = 30 days). Written as a types.Duration in responses and webhooks (e.g. {"value": 1, "unit": "months"})
2 fields of
DurationvalueintegerNumber of units; 0 means no duration
unitstringRequired when value > 0. "months" = 30 days, "years" = 365 days
oneof=seconds minutes hours days weeks months years
statusstringSubscriptionStatus (status) is where it stands: trial, trialExpired, active, pastDue, paused, stopped or cancelled. Every change sends subscription.statusChanged. Don't grant access from it: grant access while now < accessUntil (docs/api/guides/subscriptions.md § Access)
One of:
trialStatusTrial is a free trial: nothing was signed on-chain yet.trialExpiredStatusTrialExpired is a trial that ended without its customer confirming it (actionRequired confirmTrial): cancelled after the grace period unless they subscribe.activeStatusActive is a subscription billed on its due dates. Its customer may have something to do (actionRequired: topUpAllowance, raiseMaximum) while it stays active.pastDueStatusPastDue indicates a subscription whose last bill failed: its retries run.pausedStatusPaused is a subscription its customer paused (a message signed with its wallet): QBitFlow doesn't bill it until they resume it. On-chain it is still active. It keeps the period it paid for (currentPeriodEnd).stoppedStatusStopped is a subscription cancelled during its billing period (by its customer, or its merchant's force cancel with immediate=false): never billed again, it still runs until its paid period ends (nextBillingDate), then it is cancelled.cancelledStatusCancelled indicates a cancelled subscription.
actionRequiredstringOptionalActionRequired is what its customer must do, if anything (absent: nothing): topUpAllowance (the allowance covers about one more bill at most), raiseMaximum (a bill waits for a new maximum per period), confirmTrial (the trial ended unconfirmed). Every change (set, changed or cleared) sends subscription.actionRequiredChanged
One of
topUpAllowanceActionTopUpAllowance: what remains of the allowance covers about one more bill at most (or none). The customer adds periods from their management page.raiseMaximumActionRaiseMaximum: a bill is above the maximum per period the customer signed (the token's price moved); it waits up to 7 days for a new maximum, signed from the management page.confirmTrialActionConfirmTrial: the trial ended unconfirmed (trialExpired); the customer subscribes from their management page before the grace period ends.
currentPeriodEndstringOptionalCurrentPeriodEnd is the paid-through date. A trial: its end. active, paused, stopped: the end of the period paid for (the next bill's due date). pastDue: the start of the unpaid period (past). cancelled: when it was cancelled, or the end of its last paid period if that came first. For access, read accessUntil: an active subscription is renewed a few minutes after this date
accessUntilstringOptionalAccessUntil is when access ends: grant it while now < accessUntil, whatever the status. An active subscription: currentPeriodEnd plus 1 hour, since its renewal is billed within minutes of its due date (a subscriber whose bill is on its way keeps access; a failed bill sets pastDue). Every other status: currentPeriodEnd
priceUsdstringPriceUSD is the price per period the customer subscribed at, in USD (a decimal string, e.g. "9.99"), locked at signup (a free trial's too): every bill charges it and its lines (fees), converted to the token at the bill's price. A later change of the product's price applies to new subscribers only. Never "0"
feesarray of FeeLineFees is what its checkout added to the price, line by line, as the customer saw them (a tax, shipping, the processing fee when the customer pays it), locked at signup with the price: every bill charges priceUsd plus these lines. [] without any
4 fields of each
FeeLinetypestringcustom (the merchant's line) or processingFee (QBitFlow's fee, which the merchant has the customer pay)
One of:
customThe merchant's own line: a tax, shipping, a service feeprocessingFeeQBitFlow's fee, which the merchant has the customer pay (computed by QBitFlow)
labelstringThe line's name, as the checkout shows it ("Processing fee" for the processing fee)
descriptionstringOptionalOptional: more about the line, as the merchant wrote it. Omitted without one
amountUsdstringThe line's amount in USD, a decimal string with at most 2 decimals
allowanceAllowanceOptionalAllowanceStatus (allowance) is what the subscriber's wallet still lets QBitFlow bill, and the most one bill may charge, at today's price. Absent during a free trial (nothing signed yet)
6 fields of
AllowanceamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
remainingPeriodsintegerOptionalRemainingPeriods is how many whole bills the allowance still covers at today's price: the allowance divided by a bill (priceUsd and its fees, converted to the token), rounded down, before the network fee each bill adds. 0 is a value: the customer must top it up (actionRequired topUpAllowance) before the next bill. Absent without a price
maxPerPeriodstringOptionalMaxPerPeriod is the most one bill may charge, as the subscriber signed it, in the token's units (maxPerPeriodMinUnits: its on-chain integer): a bill above it waits for a new maximum (actionRequired raiseMaximum). Absent when the subscription has none recorded
maxPerPeriodMinUnitsstringOptional
lastBilledAtstringOptionalWhen it was last billed. Absent before its first bill (a trial)
nextBillingAtstringOptionalWhen its next bill is due (a trial: when it ends; stopped: when it is cancelled). Absent once cancelled
cancelledAtstringOptionalWhen it was cancelled (absent until then, and for subscriptions cancelled before it was recorded)
cancellationReasonstringOptionalWhy it stopped or was cancelled: customer, merchant, billingFailed, trialNotConverted, merchantClosed or inactiveOnChain. Absent while it runs (and for subscriptions cancelled before it was recorded)
One of:
customerCancelledByCustomer: its customer cancelled it (it was stopped until its period ended).merchantCancelledByMerchant: its merchant cancelled it (POST /subscriptions/:uuid/cancel).billingFailedCancelledBillingFailed: its bill's last attempt failed.trialNotConvertedCancelledTrialNotConverted: its trial ended and its customer didn't subscribe within the grace period.merchantClosedCancelledMerchantClosed: its merchant's space closed (a member removed, the organization closed).inactiveOnChainCancelledInactiveOnChain: the contract no longer had it active (cancelled outside QBitFlow).
cancellableAtstringOptionalMinimumCancellationDate (cancellableAt) is the earliest its customer may cancel it (the checkout's minPeriods). Absent when they may cancel it any time
metadatamapOptionalMetadata is the merchant's own key-value data: its checkout session's, copied when the subscription was created, then changed with PATCH /subscriptions/:uuid (merged). {} when none. Only for the merchant (API and webhooks)
Only for the resource's owner
managementPageLinkstringOptionalManagementPageLink is the subscription's page for its customer: where they top up its allowance, raise its maximum, pause, resume or cancel it, and confirm a trial. Share it with them (an email, the merchant's account page)
testbooleanWhether this is test data (testnets) or live data
userUuidstringOptionalMember the resource belongs to; omitted for the organization's own resources
Only for the resource's owner
customerUuidstringOptionalThe customer, when QBitFlow has one (named by the checkout, found by its customerReference, or recorded from the details the customer gave); omitted otherwise
Only for the resource's owner
customerReferencestringOptionalThe merchant's reference of the customer, as the checkout was given it (customerReference), whether or not QBitFlow has a customer with it; omitted otherwise
Only for the resource's owner
customerSummaryOptionalThe customer, named (merchant only, API reads): who paid, even once deleted. Omitted without one
Only for the resource's owner
6 fields of
SummaryuuidstringThe customer (GET /customers/:uuid)
firstNamestringFirst name
lastNamestringOptionalLast name, when given
emailstringEmail, lowercase
referencestringOptionalThe merchant's reference of the customer, when it has one
deletedbooleanThe merchant deleted the customer: it still names its rows, but GET /customers/:uuid answers 404
reasonstringinsufficientBalance, allowanceExhausted, approvalRevoked, maxAmountExceeded or other
One of:
insufficientBalanceFailureInsufficientBalance: the customer's wallet doesn't hold the bill; they add funds to it.allowanceExhaustedFailureAllowanceExhausted: what remains of the allowance the customer signed doesn't cover the bill (or it expired); they top it up (the management page).approvalRevokedFailureApprovalRevoked: the customer's wallet revoked the token approval the subscription pulls through; they approve it again (the management page).maxAmountExceededFailureMaxAmountExceeded: the bill, converted at today's price, exceeds the maximum the customer signed; they must sign a new maximum (the management page).otherFailureOther: another refusal of the charge the customer's wallet causes (the amount out of the subscription's limits, a transaction reverted on-chain); retried like the others. QBitFlow's own failures (a network or a price unavailable, its own signature refused) are retried without an event.
failureCodestringOptionalThe charge's error code (errors), e.g. insufficient_funds, insufficient_allowance
billUuidstringThe bill that failed: the uuid its subscription.billed has once it is paid (sub-hist@…)
amountUsdstringThe bill, in USD (a decimal string): the price the customer subscribed at and its lines (fees)
attemptintegerThis bill's failed attempts so far, this one included (maxAmountExceeded spends none)
remainingAttemptsintegerAttempts left: retried 1, 3, 5 and 7 days after the bill's first failed attempt (at least a day apart), and at once when the customer raises their allowance. 0: this was the last; the subscription (still pastDue here) is cancelled next (subscription.statusChanged)
nextAttemptAtstringOptionalWhen the bill is tried again at the latest (maxAmountExceeded: when it stops waiting for a new maximum); absent after the last attempt
subscription.billingFailed: example body
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "accessUntil": "2026-10-01T12:00:00Z", "allowance": { "amount": "110", "amountMinUnits": "110000000", "amountUsd": "110", "maxPerPeriod": "30", "maxPerPeriodMinUnits": "30000000", "remainingPeriods": 11 }, "amountUsd": "10", "attempt": 1, "billUuid": "sub-hist@01a0f755-f200-7000-8000-000000000005", "createdAt": "2026-09-01T12:00:00Z", "currency": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false }, "currentPeriodEnd": "2026-10-01T12:00:00Z", "customerReference": "crm-5521", "customerUuid": "01a05cd7-2a00-7d00-8000-0000000000d1", "failureCode": "insufficient_funds", "fees": [], "frequency": { "unit": "months", "value": 1 }, "from": "0xabcdef1234567890abcdef1234567890abcdef12", "lastBilledAt": "2026-09-01T12:00:00Z", "managementPageLink": "https://qbitflow.app/user/019cadfd-8900-7a00-8000-0000000000a1/manage?uuid=sub@01a05cd7-2a00-7000-8000-000000000003", "metadata": { "crm:account": "A-1042", "plan": "pro" }, "nextAttemptAt": "2026-10-02T12:00:00Z", "nextBillingAt": "2026-10-01T12:00:00Z", "priceUsd": "10", "productUuid": "019cadfd-8900-7c00-8000-0000000000c1", "reason": "insufficientBalance", "reference": "customer-7731", "remainingAttempts": 4, "status": "pastDue", "subscriptionHash": "0x2222222222222222222222222222222222222222222222222222222222222222", "test": false, "to": "0x1234567890abcdef1234567890abcdef12345678", "updatedAt": "2026-10-01T12:00:00Z", "uuid": "sub@01a05cd7-2a00-7000-8000-000000000003" }, "id": "evt_82950239-430c-595f-ad42-595955b921a0", "test": false, "type": "subscription.billingFailed", "version": "v2"}Show all 62 linesShow less
subscription.upcomingBill#
What it carries, and when
UpcomingBill: the Subscription, its billingAt (when it is billed; a trial: when it ends), amountUsd (what the bill charges, a decimal string: its priceUsd plus its fees lines), trialEnding (the trial ends then: the customer confirms the subscription to be billed), and whether the customer's wallet can pay it now, both against the bill in the token at today's price: balanceSufficient (absent for a trial, or when it couldn't be checked) and allowanceSufficient (absent for a trial, or without a price). Sent once per bill, subscriptions. days before (3 by default, 0: never), checked once a day, for active and trial subscriptions whose period (a trial: the trial) is longer than those days: none for a shorter one (a daily subscription with the 3-day default, a 2-day trial), where it would come right after the previous bill or not at all. Not for a paused one (not billed until resumed), a stopped one (it ends with its paid period) nor a cancelled one. In test mode too (a test bill runs when you trigger it)
What to do · Coming bill
Remind the customer when balanceSufficient or allowanceSufficient is false, or when trialEnding (they confirm the subscription to be billed).
34 fields of data (UpcomingBill)
uuidstringIts id (sub@…): the checkout session's
A prefixed id (pay@…, sub@…)
referencestringOptionalThe merchant's reference for the subscription (an order or customer id), set when creating its checkout. Only for the merchant: never in the public reads by uuid (GET /hosted/transactions/:txUuid)
Only for the resource's owner
createdAtstringupdatedAtstringIts last change (a bill, a status, its metadata)
fromstringThe subscriber's wallet: the one it bills. Absent when unknown (a trial not confirmed yet may have none)
tostringThe wallet its bills pay: the merchant's (or, while the organization holds a member's funds, the organization's)
productUuidstringIts product (GET /products/:uuid). Absent without one
subscriptionHashstringThe hash of the transaction that created the subscription on-chain. Absent until then (a trial, until its customer confirms it)
currencyIdintegerThe currency (token) billed, written as the currency object under currency (GET /currencies/:id)
frequencyDurationBilling interval in seconds, as on-chain (e.g. 2592000 = 30 days). Written as a types.Duration in responses and webhooks (e.g. {"value": 1, "unit": "months"})
2 fields of
DurationvalueintegerNumber of units; 0 means no duration
unitstringRequired when value > 0. "months" = 30 days, "years" = 365 days
oneof=seconds minutes hours days weeks months years
statusstringSubscriptionStatus (status) is where it stands: trial, trialExpired, active, pastDue, paused, stopped or cancelled. Every change sends subscription.statusChanged. Don't grant access from it: grant access while now < accessUntil (docs/api/guides/subscriptions.md § Access)
One of:
trialStatusTrial is a free trial: nothing was signed on-chain yet.trialExpiredStatusTrialExpired is a trial that ended without its customer confirming it (actionRequired confirmTrial): cancelled after the grace period unless they subscribe.activeStatusActive is a subscription billed on its due dates. Its customer may have something to do (actionRequired: topUpAllowance, raiseMaximum) while it stays active.pastDueStatusPastDue indicates a subscription whose last bill failed: its retries run.pausedStatusPaused is a subscription its customer paused (a message signed with its wallet): QBitFlow doesn't bill it until they resume it. On-chain it is still active. It keeps the period it paid for (currentPeriodEnd).stoppedStatusStopped is a subscription cancelled during its billing period (by its customer, or its merchant's force cancel with immediate=false): never billed again, it still runs until its paid period ends (nextBillingDate), then it is cancelled.cancelledStatusCancelled indicates a cancelled subscription.
actionRequiredstringOptionalActionRequired is what its customer must do, if anything (absent: nothing): topUpAllowance (the allowance covers about one more bill at most), raiseMaximum (a bill waits for a new maximum per period), confirmTrial (the trial ended unconfirmed). Every change (set, changed or cleared) sends subscription.actionRequiredChanged
One of
topUpAllowanceActionTopUpAllowance: what remains of the allowance covers about one more bill at most (or none). The customer adds periods from their management page.raiseMaximumActionRaiseMaximum: a bill is above the maximum per period the customer signed (the token's price moved); it waits up to 7 days for a new maximum, signed from the management page.confirmTrialActionConfirmTrial: the trial ended unconfirmed (trialExpired); the customer subscribes from their management page before the grace period ends.
currentPeriodEndstringOptionalCurrentPeriodEnd is the paid-through date. A trial: its end. active, paused, stopped: the end of the period paid for (the next bill's due date). pastDue: the start of the unpaid period (past). cancelled: when it was cancelled, or the end of its last paid period if that came first. For access, read accessUntil: an active subscription is renewed a few minutes after this date
accessUntilstringOptionalAccessUntil is when access ends: grant it while now < accessUntil, whatever the status. An active subscription: currentPeriodEnd plus 1 hour, since its renewal is billed within minutes of its due date (a subscriber whose bill is on its way keeps access; a failed bill sets pastDue). Every other status: currentPeriodEnd
priceUsdstringPriceUSD is the price per period the customer subscribed at, in USD (a decimal string, e.g. "9.99"), locked at signup (a free trial's too): every bill charges it and its lines (fees), converted to the token at the bill's price. A later change of the product's price applies to new subscribers only. Never "0"
feesarray of FeeLineFees is what its checkout added to the price, line by line, as the customer saw them (a tax, shipping, the processing fee when the customer pays it), locked at signup with the price: every bill charges priceUsd plus these lines. [] without any
4 fields of each
FeeLinetypestringcustom (the merchant's line) or processingFee (QBitFlow's fee, which the merchant has the customer pay)
One of:
customThe merchant's own line: a tax, shipping, a service feeprocessingFeeQBitFlow's fee, which the merchant has the customer pay (computed by QBitFlow)
labelstringThe line's name, as the checkout shows it ("Processing fee" for the processing fee)
descriptionstringOptionalOptional: more about the line, as the merchant wrote it. Omitted without one
amountUsdstringThe line's amount in USD, a decimal string with at most 2 decimals
allowanceAllowanceOptionalAllowanceStatus (allowance) is what the subscriber's wallet still lets QBitFlow bill, and the most one bill may charge, at today's price. Absent during a free trial (nothing signed yet)
6 fields of
AllowanceamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
remainingPeriodsintegerOptionalRemainingPeriods is how many whole bills the allowance still covers at today's price: the allowance divided by a bill (priceUsd and its fees, converted to the token), rounded down, before the network fee each bill adds. 0 is a value: the customer must top it up (actionRequired topUpAllowance) before the next bill. Absent without a price
maxPerPeriodstringOptionalMaxPerPeriod is the most one bill may charge, as the subscriber signed it, in the token's units (maxPerPeriodMinUnits: its on-chain integer): a bill above it waits for a new maximum (actionRequired raiseMaximum). Absent when the subscription has none recorded
maxPerPeriodMinUnitsstringOptional
lastBilledAtstringOptionalWhen it was last billed. Absent before its first bill (a trial)
nextBillingAtstringOptionalWhen its next bill is due (a trial: when it ends; stopped: when it is cancelled). Absent once cancelled
cancelledAtstringOptionalWhen it was cancelled (absent until then, and for subscriptions cancelled before it was recorded)
cancellationReasonstringOptionalWhy it stopped or was cancelled: customer, merchant, billingFailed, trialNotConverted, merchantClosed or inactiveOnChain. Absent while it runs (and for subscriptions cancelled before it was recorded)
One of:
customerCancelledByCustomer: its customer cancelled it (it was stopped until its period ended).merchantCancelledByMerchant: its merchant cancelled it (POST /subscriptions/:uuid/cancel).billingFailedCancelledBillingFailed: its bill's last attempt failed.trialNotConvertedCancelledTrialNotConverted: its trial ended and its customer didn't subscribe within the grace period.merchantClosedCancelledMerchantClosed: its merchant's space closed (a member removed, the organization closed).inactiveOnChainCancelledInactiveOnChain: the contract no longer had it active (cancelled outside QBitFlow).
cancellableAtstringOptionalMinimumCancellationDate (cancellableAt) is the earliest its customer may cancel it (the checkout's minPeriods). Absent when they may cancel it any time
metadatamapOptionalMetadata is the merchant's own key-value data: its checkout session's, copied when the subscription was created, then changed with PATCH /subscriptions/:uuid (merged). {} when none. Only for the merchant (API and webhooks)
Only for the resource's owner
managementPageLinkstringOptionalManagementPageLink is the subscription's page for its customer: where they top up its allowance, raise its maximum, pause, resume or cancel it, and confirm a trial. Share it with them (an email, the merchant's account page)
testbooleanWhether this is test data (testnets) or live data
userUuidstringOptionalMember the resource belongs to; omitted for the organization's own resources
Only for the resource's owner
customerUuidstringOptionalThe customer, when QBitFlow has one (named by the checkout, found by its customerReference, or recorded from the details the customer gave); omitted otherwise
Only for the resource's owner
customerReferencestringOptionalThe merchant's reference of the customer, as the checkout was given it (customerReference), whether or not QBitFlow has a customer with it; omitted otherwise
Only for the resource's owner
customerSummaryOptionalThe customer, named (merchant only, API reads): who paid, even once deleted. Omitted without one
Only for the resource's owner
6 fields of
SummaryuuidstringThe customer (GET /customers/:uuid)
firstNamestringFirst name
lastNamestringOptionalLast name, when given
emailstringEmail, lowercase
referencestringOptionalThe merchant's reference of the customer, when it has one
deletedbooleanThe merchant deleted the customer: it still names its rows, but GET /customers/:uuid answers 404
billingAtstringWhen it is billed (for a trial: when the trial ends)
amountUsdstringThe bill, in USD (a decimal string): the price the customer subscribed at and its lines (fees), converted to the token at the billing day's price
trialEndingbooleanThe trial ends then: the customer confirms the subscription (its managementPageLink) to be billed
balanceSufficientbooleanOptionalThe wallet holds at least the bill at today's price; absent for a trial (its customer chooses the wallet when confirming it), or when it couldn't be checked (price or network unavailable)
allowanceSufficientbooleanOptionalThe subscription's remaining allowance covers the bill; absent for a trial (the allowance is signed when confirming), or without a price
subscription.upcomingBill: example body
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "accessUntil": "2026-10-04T13:00:00Z", "allowance": { "amount": "110", "amountMinUnits": "110000000", "amountUsd": "110", "maxPerPeriod": "30", "maxPerPeriodMinUnits": "30000000", "remainingPeriods": 11 }, "allowanceSufficient": true, "amountUsd": "10", "balanceSufficient": true, "billingAt": "2026-10-04T12:00:00Z", "createdAt": "2026-09-04T12:00:00Z", "currency": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false }, "currentPeriodEnd": "2026-10-04T12:00:00Z", "customerReference": "crm-5521", "customerUuid": "01a05cd7-2a00-7d00-8000-0000000000d1", "fees": [], "frequency": { "unit": "months", "value": 1 }, "from": "0xabcdef1234567890abcdef1234567890abcdef12", "lastBilledAt": "2026-09-04T12:00:00Z", "managementPageLink": "https://qbitflow.app/user/019cadfd-8900-7a00-8000-0000000000a1/manage?uuid=sub@01a06c4a-3e00-7000-8000-000000000004", "metadata": { "crm:account": "A-1042", "plan": "pro" }, "nextBillingAt": "2026-10-04T12:00:00Z", "priceUsd": "10", "productUuid": "019cadfd-8900-7c00-8000-0000000000c1", "reference": "customer-7731", "status": "active", "subscriptionHash": "0x2222222222222222222222222222222222222222222222222222222222222222", "test": false, "to": "0x1234567890abcdef1234567890abcdef12345678", "trialEnding": false, "updatedAt": "2026-09-04T12:00:00Z", "uuid": "sub@01a06c4a-3e00-7000-8000-000000000004" }, "id": "evt_234c641e-08ac-5669-9147-efc5b7b97bd5", "test": false, "type": "subscription.upcomingBill", "version": "v2"}Show all 60 linesShow less
refund.requested#
What it carries, and when
Refund: a new refund (status: "pending") of a payment (txUuid: "pay@…") or of a subscription's bill (txUuid: "sub-hist@…"). initiatedBy is always customer (a request the merchant answers: a merchant's own refund is announced only by refund.approved). With what the customer paid (paid, paidMinUnits, paidUsd, network fee included) and the share asked (refundPercent, a string: "100" for a customer's request; amount, amountMinUnits, amountUsd; currency). The merchant-only read fields (approval, customer, customerUuid, productName) are API reads only
What to do · Refund
A customer's request (initiatedBy: "customer"): review it (dashboard); a merchant's own refund (initiatedBy: "merchant"): nothing to review. On approved, reverse the order, in full or in part (data.txUuid is the refunded pay@… or sub-hist@…; refundPercent < 100 is a partial refund); rejected changes nothing.
27 fields of data (Refund)
uuidstringThe refund's id (refund@<uuid>)
A prefixed id (pay@…, sub@…)
txUuidstringThe refunded transaction: a payment (pay@<uuid>) or a subscription's bill (sub-hist@<uuid>)
reasonstringOptionalWhy the refund: the customer's reason, or the merchant's when it started the refund (absent when it gave none). Only for the merchant: never in the hosted reads (GET /hosted/transactions/:txUuid/refund, the answer of POST /hosted/refunds)
Only for the resource's owner
statusstringpending (a customer's request not answered yet), approved (its transfer confirmed), rejected (the merchant rejected the request). failed is declared but never set: a failed approval leaves the request pending
One of
pendingapprovedrejectedfailedDeclared, never set: an approval that fails leaves the refund pending, to approve again
createdAtstringWhen the refund was requested (or prepared by the merchant: its intent)
merchantMessagestringOptionalThe merchant's message to the customer: why it rejected the request, or a note on a refund it started. Absent when none
respondedAtstringOptionalWhen the refund was answered: rejected, or its transfer confirmed (approved). Absent while pending
testbooleanWhether this is test data (testnets) or live data
userUuidstringOptionalMember the resource belongs to; omitted for the organization's own resources
Only for the resource's owner
txHashstringOptionalThe refund transfer's transaction, once approved. Absent before
initiatedBystringWho started the refund: customer (requested by the customer, the merchant answers) or merchant (sent by the merchant without a request: recorded once its transfer confirmed, approved; see POST /refunds/intent)
One of
customermerchantheldbooleanOptionalHeld: the refunded payment was paid to the organization's wallet, which holds the seller's funds (the trust layer): the organization decides the refund and sends it from its wallet; the seller (userUuid) sees it. Once sent, it is a negative line of the seller's held funds. False: the seller (the refund's space) decides it.
Only for the resource's owner
paidstringWhat the customer paid for the refunded transaction: its amount, fees included, and the network fee they paid on top. The base of refundPercent: a full refund sends all of it back. paid and paidMinUnits in the units of currency, paidUsd at the refunded transaction's time (absent when unknown: a request made before it was recorded) In the currency's units, exact (paidMinUnits / 10^decimals), a decimal string
paidMinUnitsstringIn the currency's smallest unit, a string of digits
paidUsdstringOptionalIn USD, a decimal string; absent when unknown
refundPercentnumberThe share of what the customer paid that the refund sends back, a percent string (100: all of it). A customer requests 100; the merchant sets it when it approves, or when it starts the refund. Written as refundPercent
amountstringWhat the refund sends back: refundPercent of what the customer paid, rounded down to a whole min unit (all of it for a customer's request, the merchant's share for its own refund, and the share sent once approved). amount in the currency's units, amountUsd at the refunded transaction's time (absent when unknown: a request made before it was recorded) In the currency's units, exact (amountMinUnits / 10^decimals), a decimal string
amountMinUnitsstringIn the currency's smallest unit, a string of digits
amountUsdstringOptionalIn USD, a decimal string; absent when unknown
currencyIdintegerThe currency paid, and sent back: written as the currency object under currency (GET /currencies/:id)
settlementTransferSettlementOptionalThe refund transfer's transaction (its block, what it cost), once approved; absent before
Only for the resource's owner
1 field of
TransferSettlementtransactionSettlementTransactionOptionalThe transfer's transaction. Absent before it was confirmed
6 fields of
SettlementTransactionblockNumberstringThe block (Ethereum, Base) or slot (Solana) it landed in, a string of digits
blockTimestringOptionalWhen its block was produced (UTC); when it was recorded if the chain's node didn't give it. Absent only on records made before it was recorded
gasUsedintegerOptionalThe gas it consumed (Ethereum, Base). Absent on Solana
feeTokenAmountWhat the transaction cost its sender, in the chain's native coin (ETH, SOL; its currency is the payment currency's chain): its gas and, on Base, its L1 data fee. amountUsd at nativePriceUsd
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
l1FeeTokenAmountOptionalThe part of fee that paid Base's L1 data fee, in the native coin. Absent off Base, and on transactions recorded before it was
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
nativePriceUsdstringOptionalNativePriceUSD is the USD price of the chain's native coin when the transaction was recorded (what its fee cost in USD). Absent when the price wasn't available then
chainstringOptionalThe chain it was paid on: ETH (Ethereum), BASE (Base) or SOL (Solana), the testnet's for a test-mode transfer (currency.test)
explorerUrlstringOptionalIts transaction on the chain's block explorer (the testnet's in test mode). Omitted without a txHash
approvalApprovalOptionalApproval is where the refund transfer stands while the request is pending and it was sent (POST /refunds/:uuid/approve, or the merchant's own refund answering it: POST /refunds): being confirmed, or its last attempt failed. Absent before it is sent, and once the refund is answered (respondedAt). Only for the merchant, in its reads (GET /refunds, GET /refunds/:uuid, GET /hosted/transactions/:txUuid/refund); never in webhooks
Only for the resource's owner
3 fields of
ApprovalstatusstringwaitingConfirmation: sent, being confirmed on-chain. created: its last attempt failed (lastAttempt): send it again (POST /refunds/:uuid/approve/intent)
One of:
createdWaiting for the customer: nothing sent yet, or the last attempt failed (lastAttempt)waitingConfirmationA transaction was sent: waiting for the network and its recordcompletedConfirmed and recorded. FinalexpiredThe checkout expired unpaid (at its expiresAt, or expired by its merchant). Final
txHashstringOptionalThe transfer being confirmed, once known
lastAttemptAttemptOptionalWith created: why the last attempt failed (its code and message)
5 fields of
Attemptstatusstringfailed
One of:
failedThe attempt's transaction wasn't sent, didn't confirm or failed on the network: nothing was paid
codestringOptionalThe error code (errors): what to do about it (insufficient_funds, token_not_approved, tx_not_sent…)
messagestringOptionalThe reason, for the customer
txHashstringOptionalThe attempt's transaction, if it was sent
atstringWhen it failed
customerUuidstringOptionalCustomerUUID is the refunded transaction's customer (a refund has none of its own); absent without one. Only for the merchant, in its reads (never in webhooks)
Only for the resource's owner
productNamestringOptionalWhat the refunded transaction paid for (its product's name, as on the payment or bill). Only for the merchant, in its reads (never in webhooks)
Only for the resource's owner
customerSummaryOptionalThe refunded transaction's customer, named (even once deleted); absent without one. Only for the merchant, in its reads (never in webhooks)
Only for the resource's owner
6 fields of
SummaryuuidstringThe customer (GET /customers/:uuid)
firstNamestringFirst name
lastNamestringOptionalLast name, when given
emailstringEmail, lowercase
referencestringOptionalThe merchant's reference of the customer, when it has one
deletedbooleanThe merchant deleted the customer: it still names its rows, but GET /customers/:uuid answers 404
refund.requested: example body
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "amount": "10.0042", "amountMinUnits": "10004200", "amountUsd": "10.0042", "chain": "BASE", "createdAt": "2026-10-01T12:00:00Z", "currency": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false }, "held": false, "initiatedBy": "customer", "paid": "10.0042", "paidMinUnits": "10004200", "paidUsd": "10.0042", "reason": "I was charged twice for the same order", "refundPercent": "100", "status": "pending", "test": false, "txUuid": "pay@01a0f755-f200-7000-8000-000000000001", "uuid": "refund@01a0ec3b-3ba0-7000-8000-000000000006" }, "id": "evt_ef774d14-7666-58e4-b447-10383579da8e", "test": false, "type": "refund.requested", "version": "v2"}Show all 36 linesShow less
refund.approved#
What it carries, and when
Refund (status: "approved", txHash, chain, explorerUrl, settlement., initiatedBy (merchant: the merchant's own refund, the first and only event about it), refundPercent and amount / amountMinUnits / amountUsd: the share actually paid back, which the merchant may have lowered when approving; Refunds § What a refund pays back). It was refund.
What to do · Refund
A customer's request (initiatedBy: "customer"): review it (dashboard); a merchant's own refund (initiatedBy: "merchant"): nothing to review. On approved, reverse the order, in full or in part (data.txUuid is the refunded pay@… or sub-hist@…; refundPercent < 100 is a partial refund); rejected changes nothing.
27 fields of data (Refund)
uuidstringThe refund's id (refund@<uuid>)
A prefixed id (pay@…, sub@…)
txUuidstringThe refunded transaction: a payment (pay@<uuid>) or a subscription's bill (sub-hist@<uuid>)
reasonstringOptionalWhy the refund: the customer's reason, or the merchant's when it started the refund (absent when it gave none). Only for the merchant: never in the hosted reads (GET /hosted/transactions/:txUuid/refund, the answer of POST /hosted/refunds)
Only for the resource's owner
statusstringpending (a customer's request not answered yet), approved (its transfer confirmed), rejected (the merchant rejected the request). failed is declared but never set: a failed approval leaves the request pending
One of
pendingapprovedrejectedfailedDeclared, never set: an approval that fails leaves the refund pending, to approve again
createdAtstringWhen the refund was requested (or prepared by the merchant: its intent)
merchantMessagestringOptionalThe merchant's message to the customer: why it rejected the request, or a note on a refund it started. Absent when none
respondedAtstringOptionalWhen the refund was answered: rejected, or its transfer confirmed (approved). Absent while pending
testbooleanWhether this is test data (testnets) or live data
userUuidstringOptionalMember the resource belongs to; omitted for the organization's own resources
Only for the resource's owner
txHashstringOptionalThe refund transfer's transaction, once approved. Absent before
initiatedBystringWho started the refund: customer (requested by the customer, the merchant answers) or merchant (sent by the merchant without a request: recorded once its transfer confirmed, approved; see POST /refunds/intent)
One of
customermerchantheldbooleanOptionalHeld: the refunded payment was paid to the organization's wallet, which holds the seller's funds (the trust layer): the organization decides the refund and sends it from its wallet; the seller (userUuid) sees it. Once sent, it is a negative line of the seller's held funds. False: the seller (the refund's space) decides it.
Only for the resource's owner
paidstringWhat the customer paid for the refunded transaction: its amount, fees included, and the network fee they paid on top. The base of refundPercent: a full refund sends all of it back. paid and paidMinUnits in the units of currency, paidUsd at the refunded transaction's time (absent when unknown: a request made before it was recorded) In the currency's units, exact (paidMinUnits / 10^decimals), a decimal string
paidMinUnitsstringIn the currency's smallest unit, a string of digits
paidUsdstringOptionalIn USD, a decimal string; absent when unknown
refundPercentnumberThe share of what the customer paid that the refund sends back, a percent string (100: all of it). A customer requests 100; the merchant sets it when it approves, or when it starts the refund. Written as refundPercent
amountstringWhat the refund sends back: refundPercent of what the customer paid, rounded down to a whole min unit (all of it for a customer's request, the merchant's share for its own refund, and the share sent once approved). amount in the currency's units, amountUsd at the refunded transaction's time (absent when unknown: a request made before it was recorded) In the currency's units, exact (amountMinUnits / 10^decimals), a decimal string
amountMinUnitsstringIn the currency's smallest unit, a string of digits
amountUsdstringOptionalIn USD, a decimal string; absent when unknown
currencyIdintegerThe currency paid, and sent back: written as the currency object under currency (GET /currencies/:id)
settlementTransferSettlementOptionalThe refund transfer's transaction (its block, what it cost), once approved; absent before
Only for the resource's owner
1 field of
TransferSettlementtransactionSettlementTransactionOptionalThe transfer's transaction. Absent before it was confirmed
6 fields of
SettlementTransactionblockNumberstringThe block (Ethereum, Base) or slot (Solana) it landed in, a string of digits
blockTimestringOptionalWhen its block was produced (UTC); when it was recorded if the chain's node didn't give it. Absent only on records made before it was recorded
gasUsedintegerOptionalThe gas it consumed (Ethereum, Base). Absent on Solana
feeTokenAmountWhat the transaction cost its sender, in the chain's native coin (ETH, SOL; its currency is the payment currency's chain): its gas and, on Base, its L1 data fee. amountUsd at nativePriceUsd
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
l1FeeTokenAmountOptionalThe part of fee that paid Base's L1 data fee, in the native coin. Absent off Base, and on transactions recorded before it was
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
nativePriceUsdstringOptionalNativePriceUSD is the USD price of the chain's native coin when the transaction was recorded (what its fee cost in USD). Absent when the price wasn't available then
chainstringOptionalThe chain it was paid on: ETH (Ethereum), BASE (Base) or SOL (Solana), the testnet's for a test-mode transfer (currency.test)
explorerUrlstringOptionalIts transaction on the chain's block explorer (the testnet's in test mode). Omitted without a txHash
approvalApprovalOptionalApproval is where the refund transfer stands while the request is pending and it was sent (POST /refunds/:uuid/approve, or the merchant's own refund answering it: POST /refunds): being confirmed, or its last attempt failed. Absent before it is sent, and once the refund is answered (respondedAt). Only for the merchant, in its reads (GET /refunds, GET /refunds/:uuid, GET /hosted/transactions/:txUuid/refund); never in webhooks
Only for the resource's owner
3 fields of
ApprovalstatusstringwaitingConfirmation: sent, being confirmed on-chain. created: its last attempt failed (lastAttempt): send it again (POST /refunds/:uuid/approve/intent)
One of:
createdWaiting for the customer: nothing sent yet, or the last attempt failed (lastAttempt)waitingConfirmationA transaction was sent: waiting for the network and its recordcompletedConfirmed and recorded. FinalexpiredThe checkout expired unpaid (at its expiresAt, or expired by its merchant). Final
txHashstringOptionalThe transfer being confirmed, once known
lastAttemptAttemptOptionalWith created: why the last attempt failed (its code and message)
5 fields of
Attemptstatusstringfailed
One of:
failedThe attempt's transaction wasn't sent, didn't confirm or failed on the network: nothing was paid
codestringOptionalThe error code (errors): what to do about it (insufficient_funds, token_not_approved, tx_not_sent…)
messagestringOptionalThe reason, for the customer
txHashstringOptionalThe attempt's transaction, if it was sent
atstringWhen it failed
customerUuidstringOptionalCustomerUUID is the refunded transaction's customer (a refund has none of its own); absent without one. Only for the merchant, in its reads (never in webhooks)
Only for the resource's owner
productNamestringOptionalWhat the refunded transaction paid for (its product's name, as on the payment or bill). Only for the merchant, in its reads (never in webhooks)
Only for the resource's owner
customerSummaryOptionalThe refunded transaction's customer, named (even once deleted); absent without one. Only for the merchant, in its reads (never in webhooks)
Only for the resource's owner
6 fields of
SummaryuuidstringThe customer (GET /customers/:uuid)
firstNamestringFirst name
lastNamestringOptionalLast name, when given
emailstringEmail, lowercase
referencestringOptionalThe merchant's reference of the customer, when it has one
deletedbooleanThe merchant deleted the customer: it still names its rows, but GET /customers/:uuid answers 404
refund.approved: example body
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "amount": "5.0021", "amountMinUnits": "5002100", "amountUsd": "5.0021", "chain": "BASE", "createdAt": "2026-09-29T08:15:00Z", "currency": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false }, "explorerUrl": "https://basescan.org/tx/0x4444444444444444444444444444444444444444444444444444444444444444", "held": false, "initiatedBy": "customer", "paid": "10.0042", "paidMinUnits": "10004200", "paidUsd": "10.0042", "reason": "I was charged twice for the same order", "refundPercent": "50", "respondedAt": "2026-10-01T12:00:00Z", "settlement": { "transaction": { "blockNumber": "52468103", "blockTime": "2026-10-01T11:59:58Z", "fee": { "amount": "0.00000114", "amountMinUnits": "1140000000000", "amountUsd": "0.005144" }, "gasUsed": 95000, "nativePriceUsd": "4512.37" } }, "status": "approved", "test": false, "txHash": "0x4444444444444444444444444444444444444444444444444444444444444444", "txUuid": "pay@01a0f755-f200-7000-8000-000000000001", "uuid": "refund@01a0ec3b-3ba0-7000-8000-000000000006" }, "id": "evt_8f63fc99-3f0c-5e75-8aef-d9b4886a51d7", "test": false, "type": "refund.approved", "version": "v2"}Show all 52 linesShow less
refund.rejected#
What it carries, and when
Refund (status: "rejected", merchantMessage when one was given, initiatedBy: "customer"): a customer's request rejected by whoever decides it (the merchant, POST /; the organization for a payment it held). A decision, final. It was refund.denied
What to do · Refund
A customer's request (initiatedBy: "customer"): review it (dashboard); a merchant's own refund (initiatedBy: "merchant"): nothing to review. On approved, reverse the order, in full or in part (data.txUuid is the refunded pay@… or sub-hist@…; refundPercent < 100 is a partial refund); rejected changes nothing.
27 fields of data (Refund)
uuidstringThe refund's id (refund@<uuid>)
A prefixed id (pay@…, sub@…)
txUuidstringThe refunded transaction: a payment (pay@<uuid>) or a subscription's bill (sub-hist@<uuid>)
reasonstringOptionalWhy the refund: the customer's reason, or the merchant's when it started the refund (absent when it gave none). Only for the merchant: never in the hosted reads (GET /hosted/transactions/:txUuid/refund, the answer of POST /hosted/refunds)
Only for the resource's owner
statusstringpending (a customer's request not answered yet), approved (its transfer confirmed), rejected (the merchant rejected the request). failed is declared but never set: a failed approval leaves the request pending
One of
pendingapprovedrejectedfailedDeclared, never set: an approval that fails leaves the refund pending, to approve again
createdAtstringWhen the refund was requested (or prepared by the merchant: its intent)
merchantMessagestringOptionalThe merchant's message to the customer: why it rejected the request, or a note on a refund it started. Absent when none
respondedAtstringOptionalWhen the refund was answered: rejected, or its transfer confirmed (approved). Absent while pending
testbooleanWhether this is test data (testnets) or live data
userUuidstringOptionalMember the resource belongs to; omitted for the organization's own resources
Only for the resource's owner
txHashstringOptionalThe refund transfer's transaction, once approved. Absent before
initiatedBystringWho started the refund: customer (requested by the customer, the merchant answers) or merchant (sent by the merchant without a request: recorded once its transfer confirmed, approved; see POST /refunds/intent)
One of
customermerchantheldbooleanOptionalHeld: the refunded payment was paid to the organization's wallet, which holds the seller's funds (the trust layer): the organization decides the refund and sends it from its wallet; the seller (userUuid) sees it. Once sent, it is a negative line of the seller's held funds. False: the seller (the refund's space) decides it.
Only for the resource's owner
paidstringWhat the customer paid for the refunded transaction: its amount, fees included, and the network fee they paid on top. The base of refundPercent: a full refund sends all of it back. paid and paidMinUnits in the units of currency, paidUsd at the refunded transaction's time (absent when unknown: a request made before it was recorded) In the currency's units, exact (paidMinUnits / 10^decimals), a decimal string
paidMinUnitsstringIn the currency's smallest unit, a string of digits
paidUsdstringOptionalIn USD, a decimal string; absent when unknown
refundPercentnumberThe share of what the customer paid that the refund sends back, a percent string (100: all of it). A customer requests 100; the merchant sets it when it approves, or when it starts the refund. Written as refundPercent
amountstringWhat the refund sends back: refundPercent of what the customer paid, rounded down to a whole min unit (all of it for a customer's request, the merchant's share for its own refund, and the share sent once approved). amount in the currency's units, amountUsd at the refunded transaction's time (absent when unknown: a request made before it was recorded) In the currency's units, exact (amountMinUnits / 10^decimals), a decimal string
amountMinUnitsstringIn the currency's smallest unit, a string of digits
amountUsdstringOptionalIn USD, a decimal string; absent when unknown
currencyIdintegerThe currency paid, and sent back: written as the currency object under currency (GET /currencies/:id)
settlementTransferSettlementOptionalThe refund transfer's transaction (its block, what it cost), once approved; absent before
Only for the resource's owner
1 field of
TransferSettlementtransactionSettlementTransactionOptionalThe transfer's transaction. Absent before it was confirmed
6 fields of
SettlementTransactionblockNumberstringThe block (Ethereum, Base) or slot (Solana) it landed in, a string of digits
blockTimestringOptionalWhen its block was produced (UTC); when it was recorded if the chain's node didn't give it. Absent only on records made before it was recorded
gasUsedintegerOptionalThe gas it consumed (Ethereum, Base). Absent on Solana
feeTokenAmountWhat the transaction cost its sender, in the chain's native coin (ETH, SOL; its currency is the payment currency's chain): its gas and, on Base, its L1 data fee. amountUsd at nativePriceUsd
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
l1FeeTokenAmountOptionalThe part of fee that paid Base's L1 data fee, in the native coin. Absent off Base, and on transactions recorded before it was
3 fields of
TokenAmountamountstringIn the token's units, exact: amountMinUnits / 10^decimals, a decimal string ("12.5")
amountMinUnitsstringThe on-chain integer, in the token's smallest unit, a string of digits ("12500000")
amountUsdstringOptionalIts value in USD, a decimal string; absent when no price was known
nativePriceUsdstringOptionalNativePriceUSD is the USD price of the chain's native coin when the transaction was recorded (what its fee cost in USD). Absent when the price wasn't available then
chainstringOptionalThe chain it was paid on: ETH (Ethereum), BASE (Base) or SOL (Solana), the testnet's for a test-mode transfer (currency.test)
explorerUrlstringOptionalIts transaction on the chain's block explorer (the testnet's in test mode). Omitted without a txHash
approvalApprovalOptionalApproval is where the refund transfer stands while the request is pending and it was sent (POST /refunds/:uuid/approve, or the merchant's own refund answering it: POST /refunds): being confirmed, or its last attempt failed. Absent before it is sent, and once the refund is answered (respondedAt). Only for the merchant, in its reads (GET /refunds, GET /refunds/:uuid, GET /hosted/transactions/:txUuid/refund); never in webhooks
Only for the resource's owner
3 fields of
ApprovalstatusstringwaitingConfirmation: sent, being confirmed on-chain. created: its last attempt failed (lastAttempt): send it again (POST /refunds/:uuid/approve/intent)
One of:
createdWaiting for the customer: nothing sent yet, or the last attempt failed (lastAttempt)waitingConfirmationA transaction was sent: waiting for the network and its recordcompletedConfirmed and recorded. FinalexpiredThe checkout expired unpaid (at its expiresAt, or expired by its merchant). Final
txHashstringOptionalThe transfer being confirmed, once known
lastAttemptAttemptOptionalWith created: why the last attempt failed (its code and message)
5 fields of
Attemptstatusstringfailed
One of:
failedThe attempt's transaction wasn't sent, didn't confirm or failed on the network: nothing was paid
codestringOptionalThe error code (errors): what to do about it (insufficient_funds, token_not_approved, tx_not_sent…)
messagestringOptionalThe reason, for the customer
txHashstringOptionalThe attempt's transaction, if it was sent
atstringWhen it failed
customerUuidstringOptionalCustomerUUID is the refunded transaction's customer (a refund has none of its own); absent without one. Only for the merchant, in its reads (never in webhooks)
Only for the resource's owner
productNamestringOptionalWhat the refunded transaction paid for (its product's name, as on the payment or bill). Only for the merchant, in its reads (never in webhooks)
Only for the resource's owner
customerSummaryOptionalThe refunded transaction's customer, named (even once deleted); absent without one. Only for the merchant, in its reads (never in webhooks)
Only for the resource's owner
6 fields of
SummaryuuidstringThe customer (GET /customers/:uuid)
firstNamestringFirst name
lastNamestringOptionalLast name, when given
emailstringEmail, lowercase
referencestringOptionalThe merchant's reference of the customer, when it has one
deletedbooleanThe merchant deleted the customer: it still names its rows, but GET /customers/:uuid answers 404
refund.rejected: example body
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "amount": "10.0042", "amountMinUnits": "10004200", "amountUsd": "10.0042", "chain": "BASE", "createdAt": "2026-09-29T08:15:00Z", "currency": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false }, "held": false, "initiatedBy": "customer", "merchantMessage": "Only one payment was received for this order", "paid": "10.0042", "paidMinUnits": "10004200", "paidUsd": "10.0042", "reason": "I was charged twice for the same order", "refundPercent": "100", "respondedAt": "2026-10-01T12:00:00Z", "status": "rejected", "test": false, "txUuid": "pay@01a0f755-f200-7000-8000-000000000001", "uuid": "refund@01a0ec3b-3ba0-7000-8000-000000000006" }, "id": "evt_cc682edc-ba17-5d1a-8f4f-a7534c2f4783", "test": false, "type": "refund.rejected", "version": "v2"}Show all 38 linesShow less
member.joined#
What it carries, and when
JoinedMember: the member (GET /: firstName, lastName?, spaceUuid, acceptedCurrencies, organizationFeePercent, trustedAt?), in the mode they joined, and invitationUuid, the invitation they accepted (POST / answered it): match the event to it
What to do · Marketplace seller
Mark the seller onboarded and store data.userUuid (for On-Behalf-Of); stop selling for a removed one; reconcile what you paid out (Marketplace).
11 fields of data (JoinedMember)
userUuidstringfirstNamestringlastNamestringAbsent when the account has none
emailstringtestbooleanThe mode
organizationFeePercentnumberThe organization's fee on the member's payments in this mode, in bps. Written as organizationFeePercent ("0": none)
trustedAtstringOptionalWhen the organization started paying them directly; absent while it holds their funds
joinedAtstringWhen they joined in this mode
acceptedCurrencyIdsarray of integerThe currencies (GET /currencies/:id) a checkout of the member can be paid in now: those their wallets accept, always within the organization's (the organization's own while it holds their funds: its wallets receive their payments). Subscriptions take the tokens among them. Empty: a checkout of theirs answers 409 merchant_not_ready. Written as acceptedCurrencies, the currency objects
spaceUuidstringTheir personal space in this mode: what POST /auth/switch-space takes for an owner or admin to act as them
invitationUuidstringThe invitation they accepted (POST /invitations answered its uuid): what matches the event to it
member.joined: example body
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "acceptedCurrencies": [ { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false } ], "email": "alice@example.com", "firstName": "Alice", "invitationUuid": "01a0f755-f200-7000-8000-00000000000a", "joinedAt": "2026-10-01T12:00:00Z", "lastName": "Martin", "organizationFeePercent": "5", "spaceUuid": "019eca82-5680-7b00-8000-0000000000b2", "test": false, "userUuid": "019eca82-5680-7b00-8000-0000000000b1" }, "id": "evt_634bf7f1-010f-5f60-8232-06c30f013f28", "test": false, "type": "member.joined", "version": "v2"}Show all 31 linesShow less
member.removed#
What it carries, and when
Member (with spaceUuid and acceptedCurrencies), as they were before their removal from that mode
What to do · Marketplace seller
Mark the seller onboarded and store data.userUuid (for On-Behalf-Of); stop selling for a removed one; reconcile what you paid out (Marketplace).
10 fields of data (Member)
userUuidstringfirstNamestringlastNamestringAbsent when the account has none
emailstringtestbooleanThe mode
organizationFeePercentnumberThe organization's fee on the member's payments in this mode, in bps. Written as organizationFeePercent ("0": none)
trustedAtstringOptionalWhen the organization started paying them directly; absent while it holds their funds
joinedAtstringWhen they joined in this mode
acceptedCurrencyIdsarray of integerThe currencies (GET /currencies/:id) a checkout of the member can be paid in now: those their wallets accept, always within the organization's (the organization's own while it holds their funds: its wallets receive their payments). Subscriptions take the tokens among them. Empty: a checkout of theirs answers 409 merchant_not_ready. Written as acceptedCurrencies, the currency objects
spaceUuidstringTheir personal space in this mode: what POST /auth/switch-space takes for an owner or admin to act as them
member.removed: example body
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "acceptedCurrencies": [ { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false } ], "email": "alice@example.com", "firstName": "Alice", "joinedAt": "2026-06-15T09:00:00Z", "lastName": "Martin", "organizationFeePercent": "5", "spaceUuid": "019eca82-5680-7b00-8000-0000000000b2", "test": false, "trustedAt": "2026-07-01T09:00:00Z", "userUuid": "019eca82-5680-7b00-8000-0000000000b1" }, "id": "evt_d32aec3c-d46c-57e3-9292-f8433766df46", "test": false, "type": "member.removed", "version": "v2"}Show all 31 linesShow less
heldFunds.released#
What it carries, and when
The transfer the member received (amount + amountMinUnits in the token, amountUsd, currency, chain, explorerUrl, settlement.), and ledgers, the lines it settled, oldest first (LedgerEntry, as GET / lists them, Held funds): the payments it paid (positive, type: "payment") and the refunds of them it took off (negative, type: "refund"), each with what it counted for (owed, owedMinUnits, owedUsd). amount is their sum
What to do · Marketplace seller
Mark the seller onboarded and store data.userUuid (for On-Behalf-Of); stop selling for a removed one; reconcile what you paid out (Marketplace).
heldFunds.released: example body
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "amount": "98.9068", "amountMinUnits": "98906800", "amountUsd": "98.9068", "chain": "BASE", "createdAt": "2026-10-01T12:00:00Z", "currency": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false }, "explorerUrl": "https://basescan.org/tx/0x5555555555555555555555555555555555555555555555555555555555555555", "from": "0x9876543210fedcba9876543210fedcba98765432", "ledgers": [ { "amountUsd": "100", "createdAt": "2026-09-28T12:00:00Z", "currency": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false }, "description": "Payment pay@01a0f755-f200-7000-8000-000000000001", "owed": "99.01", "owedMinUnits": "99010000", "owedUsd": "99.01", "settlement": { "feePercent": "0.99", "split": { "merchant": { "amount": "99.01", "amountMinUnits": "99010000", "amountUsd": "99.01" }, "networkFee": { "amount": "0", "amountMinUnits": "0", "amountUsd": "0" }, "platform": { "amount": "0.99", "amountMinUnits": "990000", "amountUsd": "0.99" } }, "transaction": { "blockNumber": "52468103", "blockTime": "2026-09-28T11:59:58Z", "fee": { "amount": "0.00000114", "amountMinUnits": "1140000000000", "amountUsd": "0.005144" }, "gasUsed": 95000, "nativePriceUsd": "4512.37" } }, "txUuid": "pay@01a0f755-f200-7000-8000-000000000001", "type": "payment" }, { "amountUsd": "10", "createdAt": "2026-09-29T12:00:00Z", "currency": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false }, "description": "Payment pay@01a0d6d7-c600-7000-8000-00000000000c", "owed": "9.901", "owedMinUnits": "9901000", "owedUsd": "9.901", "settlement": { "feePercent": "0.99", "split": { "merchant": { "amount": "9.901", "amountMinUnits": "9901000", "amountUsd": "9.901" }, "networkFee": { "amount": "0.0042", "amountMinUnits": "4200", "amountUsd": "0.0042" }, "platform": { "amount": "0.099", "amountMinUnits": "99000", "amountUsd": "0.099" } }, "transaction": { "blockNumber": "52468103", "blockTime": "2026-09-29T11:59:58Z", "fee": { "amount": "0.00000114", "amountMinUnits": "1140000000000", "amountUsd": "0.005144" }, "gasUsed": 95000, "nativePriceUsd": "4512.37" } }, "txUuid": "pay@01a0d6d7-c600-7000-8000-00000000000c", "type": "payment" }, { "amountUsd": "10.0042", "createdAt": "2026-09-30T12:00:00Z", "currency": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false }, "description": "Refund of payment pay@01a0d6d7-c600-7000-8000-00000000000c", "owed": "-10.0042", "owedMinUnits": "-10004200", "owedUsd": "-10.0042", "refundedTxUuid": "pay@01a0d6d7-c600-7000-8000-00000000000c", "txUuid": "refund@01a0ec3b-3ba0-7000-8000-000000000006", "type": "refund" } ], "received": true, "settlement": { "transaction": { "blockNumber": "52468103", "blockTime": "2026-10-01T11:59:58Z", "fee": { "amount": "0.00000114", "amountMinUnits": "1140000000000", "amountUsd": "0.005144" }, "gasUsed": 95000, "nativePriceUsd": "4512.37" } }, "test": false, "to": "0x1234567890abcdef1234567890abcdef12345678", "txHash": "0x5555555555555555555555555555555555555555555555555555555555555555", "type": "heldFundsRelease", "userUuid": "019eca82-5680-7b00-8000-0000000000b1", "uuid": "transfer@01a0f755-f200-7000-8000-000000000007" }, "id": "evt_620352e2-0285-52e8-adc1-64a9774278c1", "test": false, "type": "heldFunds.released", "userUuid": "019eca82-5680-7b00-8000-0000000000b1", "version": "v2"}Show all 176 linesShow less
checkout.expired#
What it carries, and when
The checkout session as GET / shows it, with your metadata: uuid (pay@…/sub@…, the id the payment or subscription would have had), txType, reference, the product (productUuid, productReference, productName, description, priceUsd), amountUsd (the price and its fees), fees, availableCurrencies (currency objects), successUrl/cancelUrl (your URLs, placeholders filled), organizationName, test, createdAt, expiresAt, metadata (the hosted checkout's own fields, asksCustomerDetails and customerDetails, are not sent). No customer is named (match it on uuid, reference or metadata). Sent once per opened session, at its expiresAt, when it wasn't paid (a payment sent right before is waited for), or when you expire it (POST /: expiresAt is then when you did). A subscription checkout's data carries its terms too (frequency, trialPeriod, minPeriods, upgradingFromTrial). A session opened by a product's link (GET /) is watched from its first intent: a visit nobody paid from sends nothing. The session confirming a free trial (upgradingFromTrial: true) sends nothing when it expires: the trial goes on
What to do · Abandoned checkout
Release what the order held (stock, a reservation): it can no longer be paid. A failed attempt (lastAttempt on the session) is not the end: the customer may still pay until it expires.
checkout.expired: example body
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "amountUsd": "10", "availableCurrencies": [ { "chain": "BASE", "code": "BASE", "decimals": 18, "id": 3, "name": "Base", "symbol": "BASE", "test": false }, { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "chain": "BASE", "code": "BASE:USDC", "decimals": 6, "id": 8, "mainCurrencyId": 3, "name": "USD Coin", "symbol": "USDC", "test": false } ], "cancelUrl": "https://shop.example.com/cart", "createdAt": "2026-09-30T12:00:00Z", "description": "Lifetime access to the Pro plan", "expiresAt": "2026-10-01T12:00:00Z", "fees": [], "metadata": { "crm:customer": "C-5521", "orderId": "order-1043" }, "organizationName": "Example Shop", "priceUsd": "10", "productName": "Pro plan (lifetime)", "productUuid": "019cadfd-8900-7c00-8000-0000000000c1", "reference": "order-1043", "successUrl": "https://qbitflow.app/user/019cadfd-8900-7a00-8000-0000000000a1/manage?uuid=pay@01a0f22f-9600-7000-8000-000000000008&redirectUrl=https%3A%2F%2Fshop.example.com%2Fthanks", "test": false, "txType": "payment", "uuid": "pay@01a0f22f-9600-7000-8000-000000000008" }, "id": "evt_63b9afe7-5561-5c7e-b2c5-42b4d58ecba8", "test": false, "type": "checkout.expired", "version": "v2"}Show all 50 linesShow less
webhook.test#
What it carries, and when
TestData: sent only by POST /
2 fields of data (TestData)
endpointUuidstringmessagestring
webhook.test: example body
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "endpointUuid": "019cadfd-8900-7e00-8000-0000000000e1", "message": "A test event from the dashboard: nothing happened" }, "id": "evt_bd1a913d-188e-5ac5-a2d6-5fd25cd67301", "test": false, "type": "webhook.test", "version": "v2"}Verify the signature
The SDKs verify every delivery locally, with no call.
const secret = process. env. QBITFLOW_WEBHOOK_SECRET!;try { // rawBody: the body exactly as received; signatureHeader: its QBitFlow-Signature header. const event = webhooks.constructEvent(rawBody, signatureHeader, secret); if (event.type === 'payment.completed') { console.log(`fulfil order ${event.data.reference}`); // event.data is a PaymentCompleted } return 200; // acknowledge every event, the types you ignore included} catch (err) { if (err instanceof WebhookSignatureError) { console.log(`rejected: ${err.reason}`); // e.g. noMatchingSignature return 400; } if (err instanceof ValidationError) { console.log(`not a v2 event: ${err.message}`); return 400; } throw err;}try: event = qbitflow. webhooks. construct_event( raw_body, # the raw bytes as received, never re-serialized signature_header, # the QBitFlow-Signature header os. environ["QBITFLOW_WEBHOOK_SECRET"], )except qbitflow.WebhookSignatureError as exc: print("rejected:", exc.reason) # e.g. noMatchingSignature, timestampOutsideTolerance return 400except qbitflow.ValidationError: return 400 # not a v2 eventif isinstance(event, qbitflow.PaymentCompletedEvent): # narrow the union by class print("fulfil order", event.data.reference)return 200 # acknowledge every other type too, or QBitFlow retries itwebhook_verify/main.go
func handleWebhook(w http.ResponseWriter, r *http.Request) { // The raw body, exactly as received: the signature covers its bytes. rawBody, err := io. ReadAll( http. MaxBytesReader( w, r.Body, 1<<20)) if err != nil { http.Error(w, "cannot read the body", http.StatusBadRequest) return } event, err := qbitflow.ConstructEvent(rawBody, r. Header. Get( qbitflow. SignatureHeader), os. Getenv( "QBITFLOW_WEBHOOK_SECRET")) var sigErr *qbitflow.WebhookSignatureError if errors.As(err, &sigErr) { log.Printf("rejected: %s", sigErr.Reason) // e.g. noMatchingSignature, timestampOutsideTolerance http.Error(w, "invalid signature", http.StatusBadRequest) return } if err != nil { http.Error(w, "invalid event", http.StatusBadRequest) // not a v2 event return } if event.Type == qbitflow. EventTypePaymentCompleted { payment, err := event.PaymentCompleted() if err != nil { http.Error(w, "invalid event", http.StatusBadRequest) return } // Deliveries are at least once: fulfil the order once per event.ID. log.Printf("event %s: order %q paid", event.ID, payment.Reference) } w.WriteHeader(http.StatusOK) // to the types you ignore too}$rawBody = (string) file_get_contents( 'php://input'); // the bytes as received: never re-serializetry { $event = \QBitFlow\Webhooks\Webhook::constructEvent( $rawBody, $_SERVER['HTTP_QBITFLOW_SIGNATURE'] ?? '', (string) getenv( 'QBITFLOW_WEBHOOK_SECRET'), );} catch ( \QBitFlow\Exceptions\WebhookSignatureException $e) { http_response_code(400); // $e->reason: noMatchingSignature, timestampOutsideTolerance, … exit;} catch ( \QBitFlow\Exceptions\ValidationException) { http_response_code(400); // not a v2 event (unsupported payload version), or not JSON exit;} if ($event instanceof \QBitFlow\Events\PaymentCompletedEvent) { // Deliveries are at least once: skip an $event->id you already processed. error_log("fulfil order {$event->data->reference} ({$event->data->uuid})");}http_response_code(200); // to every type, the ignored ones tooEvery delivery is a POST with a JSON body (Content-Type: application/json, canonical JSON:
sorted keys, no spaces) and these headers:
| Header | Value |
|---|---|
QBitFlow-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256(secret, t + "." + body)> (v1 names the signature scheme, not an API version); for 24 hours after a rotation, a second v1= with the previous secret (t=…,v1=<new>,v1=<previous>) |
QBitFlow-Event-Id | the event id (evt_…) |
QBitFlow-Event-Type | the event type |
QBitFlow-Webhook-Version | the body's version: v2 |
User-Agent | QBitFlow-Webhooks/2 |
Deliveries come from QBitFlow's cloud workers, without fixed source IP addresses: don't allow-list IPs, verify the signature.
Verify (what the SDK helpers do, no call needed). The receiver holds one secret, the one it was configured with; the header may carry several signatures:
- Read the raw body as received (don't re-serialize parsed JSON).
- Split
QBitFlow-Signatureon,, each part on its first=, trimming spaces: keeptand everyv1value, ignore any other key (a future scheme; never fall back to another one). Not, or nov1→ invalid. - Refuse a
tmore than 5 minutes from your clock, either way (replays). Each attempt is signed afresh, so a retry carries a newt. - Compute once
expected = hex(HMAC-SHA256(secret, t + "." + rawBody)), lowercase. - Compare
expectedwith eachv1, in constant time; valid if any matches. Don't stop at the firstv1, and don't take the last: during a rotation the receiver's secret may match either.
After a rotation the header is t=…,v1=<new secret's>,v1=<previous secret's> for 24 hours, then
t=…,v1=<new secret's> again. A receiver still configured with the previous secret matches the
second signature, one already updated matches the first: it switches secrets whenever it likes
within the day, without losing a delivery, and there is nothing to configure on its side. Only
one previous secret is kept: rotating again within those 24 hours ends the first overlap at
once (the header then carries the newest secret and the one just replaced), so a receiver still
on the oldest secret starts failing; rotate once, update, then rotate again if needed.
POST /webhooks/verify runs the same check server-side with the endpoint's current secret
(whsec_…, from its creation or GET /webhooks/endpoints/:uuid/secret).
Worked example (the SDKs' test vector; core/src/app/events/events_test.go checks it):
| raw body | {"createdAt":"2026-10-01T12:00:00Z","data":{},"id":"evt_3f1c2d4e-5a6b-5c7d-8e9f-0a1b2c3d4e5f","test":false,"type":"webhook.test","version":"v2"} |
t | 1790856000 |
new secret → v1 | whsec_new_secret → 4a158046f55556e922bdec376a917c3ac338ba575b495f825427541a60bd2f4d |
previous secret → v1 | whsec_old_secret → 1cc78a6297ad96dd282d9d0399def50ed42a4e651885ac79f8528194227bc4f5 |
| header during the rotation | t=1790856000,v1=4a158046…2f4d,v1=1cc78a62…c4f5 (the two values above, in full) |
With the clock at t + 60 s: whsec_new_secret and whsec_old_secret are both valid, any other
secret is invalid; at t + 6 min every secret is invalid (too old).
Delivery and retries
- Any
2xxis delivered. Anything else is retried: another status (a redirect, a4xx, a5xx), a timeout or a network error. Answer2xxto the events you ignore. - Live mode: retries come 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours after the failed attempt, then every 12 hours, for 3 days after the first attempt (11 attempts). Test mode: 3 retries in a few hours (5 minutes, 30 minutes, 2 hours after the failed attempt). Then the event is abandoned for that endpoint. These are Stripe's rules.
- Every attempt uses the endpoint's current URL and secret. Deliveries survive restarts of QBitFlow.
- One event per request, delivered at least once. The same event can reach an endpoint more
than once: an attempt whose
2xxdidn't reach QBitFlow (the connection dropped after your server processed it, or it answered after the timeout), a restart on QBitFlow's side between a post and its record, or a manual resend. Deduplicate onid(theQBitFlow-Event-Idheader carries it too) and make your handler idempotent. - A manual resend (
POST /webhooks/events/:id/resend, Webhooks § Event log) that delivers an event ends its automatic retries to that endpoint: the next one sends nothing. - Deliveries have a 30-second timeout (connection, headers and body together; the connection must
open within 10 seconds, then your answer has the rest of the 30), never follow
redirects, and never go to private, loopback or other non-public addresses. Answer fast:
store the event, answer
2xx, then process it asynchronously (a queue, a background job); an answer after 30 seconds is a failed attempt, retried even if your server processed it. - Order is kept per endpoint within one publication (
subscription.createdbefore its firstsubscription.billed): the next event waits until the previous one is delivered or abandoned. While it waits, the event log shows itpendingwithwaitingForthe earlier event, and no attempt yet (Webhooks § Event log). Across publications there is no order, and nothing waits: a renewal'ssubscription.billednever waits for an earlier event your endpoint is failing. UsecreatedAtand the resources' own timestamps. - An endpoint disabled (
PATCH …/:uuid {"enabled": false}, or by QBitFlow, below) or deleted receives nothing more: a delivery in progress finds it so at its next attempt and stops, for the rest of its events too. Enabled again before that attempt, the delivery goes on (as Stripe does). Events published while it is disabled are never sent to it.
Failing endpoints
- An endpoint whose every attempt has failed for 24 hours is reported by email to its space's
team (each owner and admin; for a member's endpoint, the member), at most once a day while it
fails (a UTC calendar day: the check runs hourly, so the next email can come soon after
midnight UTC). The setting
emails.webhookFailures(on by default) turns these emails off. - After 3 days without a successful delivery, the endpoint is disabled:
disabledAtis set, an email tells its team (same setting), and it receives nothing more.PATCH /webhooks/endpoints/:uuid {"enabled": true}enables it again; the failures before don't count anymore. - A disabled endpoint isn't checked any more: no email about it until it is enabled again (and fails again for a day).
- Test-mode endpoints are never checked: a test endpoint is usually only reachable while its
developer tests, so failing between tests is expected. It is never disabled for failing and no
email is sent about it; its
failingSincestill says how it stands. Each failed delivery is still retried 3 times (§ Delivery and retries).
const router = webhooks . router( process. env. QBITFLOW_WEBHOOK_SECRET!, { // Every 400 (bad signature or body: event null) and 500 (a handler threw). The router // answers without the details: log them here. onError: (event, err) => console.error('webhook rejected:', event?.id, err.message), }) .on('payment.completed', async (payment, event) => { await fulfilOrder(payment.reference, event.id); // idempotent: deduplicates on event.id }); // nodeHandler() reads the raw body itself (at most 1 MiB): mount it before any body parser.const handleWebhook = router.nodeHandler();createServer((req, res) => { if (req.url === '/webhooks/qbitflow') void handleWebhook(req, res); else res.writeHead(404).end();}).listen(8080);router = qbitflow. WebhookRouter( os. environ["QBITFLOW_WEBHOOK_SECRET"]) @router.on("payment.completed") # the handler gets the typed data, then the eventdef fulfil(data: qbitflow.PaymentCompleted, event: qbitflow.Event) -> None: # At least once: the same event can arrive twice, deduplicate on event.id. mark_order_paid(data.reference or data.uuid, event_id=event.id) app = FastAPI()# Verifies the QBitFlow-Signature header on the raw body, parses the event, runs the handler# for its type, and answers the status QBitFlow expects (200, 400 or 500).app. add_api_route( "/webhooks/qbitflow", router.fastapi_endpoint(), methods=["POST"])webhook_handler/main.go
var fulfilled sync.Map // stands for your database: deliveries are at least oncerouter := qbitflow. NewWebhookRouter( os. Getenv( "QBITFLOW_WEBHOOK_SECRET"), qbitflow. WithWebhookErrorHandler( func( event *qbitflow.Event, err error) { log.Printf("webhook refused or failed: %v", err) // event is nil when the body could not be parsed }),). OnPaymentCompleted(func(ctx context.Context, payment *qbitflow.PaymentCompleted, event *qbitflow.Event) error { if _, done := fulfilled.LoadOrStore(event.ID, true); done { return nil // a retry of an event already handled } log.Printf("fulfil order %q: %s USD (%s, %v)", payment.Reference, payment.AmountUSD, payment.UUID, payment.Metadata) return nil // an error answers 500, and QBitFlow delivers the event again }). OnCheckoutExpired(func(ctx context.Context, expired *qbitflow.CheckoutExpired, event *qbitflow.Event) error { if expired.Payment != nil { log.Printf("release order %q", expired.Payment.Reference) } return nil }) // The router is an http.Handler: it verifies the QBitFlow-Signature header on the raw body,// parses the event, runs the handlers of its type and answers 200, 400 or 500.http.Handle("POST /webhooks/qbitflow", router)$router = (new \QBitFlow\Webhooks\WebhookRouter( ( string) getenv( 'QBITFLOW_WEBHOOK_SECRET'))) ->on( \QBitFlow\Enums\EventType::PAYMENT_COMPLETED, function ( \QBitFlow\Models\PaymentCompleted $payment, \QBitFlow\Events\Event $event, ): void { // Deliveries are at least once: skip an $event->id you already processed. error_log("event {$event->id}: fulfil order {$payment->reference} ({$payment->uuid}), {$payment->amountUsd} USD"); }) ->on( \QBitFlow\Enums\EventType::SUBSCRIPTION_STATUS_CHANGED, function ( \QBitFlow\Models\SubscriptionStatusChanged $subscription, ): void { error_log("{$subscription->uuid} is {$subscription->status}, access: " . ($subscription->hasAccess() ? 'yes' : 'no')); }) ->onError(fn (?\QBitFlow\Events\Event $event, \Throwable $error) => error_log("webhook refused or failed: {$error->getMessage()}")); // Reads php://input and the QBitFlow-Signature header, runs the handler of the event's type, and// answers 200 (ignored types too), 400 (bad signature) or 500 (a handler threw: QBitFlow retries).$router->handleGlobals();