Skip to Content
Payments and receipts

Payments and receipts

Nibgate earnings are payment analytics, not withdrawals. Funds flow to the creator’s on-chain fee wallet contract, which enforces a 99% creator / 1% treasury split — see Revenue model.

Payment model

With hosted pay enabled, the hub resolves each creator’s payment destination to their GatewayFeeWallet contract (deployed deterministically per creator). A creator can run multiple sites, and every site resolves to the same fee wallet for that creator.

Nibgate does not need to custody funds to show earnings. It records payment events and receipt references after the creator site completes an unlock. Receipts store the gross amount plus a derived protocolFee (1% under hosted pay), so analytics match the fee wallet contract’s on-chain 99/1 split — see Revenue model.

Unlock model

One reliable path powers every rail:

one resource -> one payment -> one verified unlock receipt -> one entitlement -> one unlock proof

The entitlement is lifetime for paid unlocks: once a receipt exists, the wallet keeps access even after the proof expires or browser storage clears (the server re-issues a fresh proof on every legit visit). Free grants are re-granted per visit and are not lifetime. Metered streaming, metered reading, passes, and agent quotas are future unlock strategies; they should still produce receipt events that can be indexed by Nibgate.

Unlock proofs (the short-lived cache)

Proofs are the browser/agent cache over the database; they are bindable and short-lived. Full grant logic lives on Whitelists & access control.

  • Nibshare — server-minted: {wallet}.{iat}.{exp}.{mac}, mac = HMAC-SHA256("nibshare:{shareId}:{wallet}:{iat}:{exp}") keyed by NIBGATE_GATEWAY_SECRET/NIBGATE_PROOF_SECRET. 12h window.
  • Subblogs — SDK-signed: unlock token minted by createNibgateServer(...) after proof verification, 12h TTL.

An expired proof is harmless — entitlements + receipts are checked before proof freshness, so the server simply re-mints on the next legit visit. The x-nibgate-payment-proof header carries the proof to media/document routes; ?wallet= on the query can authenticate <img>/<audio> tags that cannot send headers.

Payment idempotency: one grant per payment

x402 has no native payment id — the settlement txHash is the only stable identity. Both backends enforce a unique (paymentNonce=txHash, resourceId) index and do find-or-create on grant. A replayed proof hits the stored receipt and returns {receipt, replay: true} — no second grant, no double unlock count, no double event. This is the #1 x402 production failure mode (248 grants per single payment measured on a live endpoint without the guard).

Receipt types

x402 gateway receipt

Use this when the unlock flow completes through a gateway-style x402 payment flow. Store the gateway receipt id or proof URL when available.

Headless x402 (agent/API flow)

For server-to-server or AI agent payments, use GatewayClient from @circle-fin/x402-batching/client:

import { GatewayClient } from '@circle-fin/x402-batching/client' const agent = new GatewayClient({ chain: 'arcTestnet', privateKey: process.env.AGENT_KEY, rpcUrl: 'https://rpc.testnet.arc.io' }) const result = await agent.pay(contentAccessUrl, { headers: { 'x-nibgate-actor': 'agent' } })

The GatewayClient.pay() method auto-handles the 402 challenge → EIP-3009 signing → retry → settlement flow.

Direct transfer receipt

Use the direct rail when the buyer’s wallet sends USDC straight to the creator’s receiver (no Gateway facilitator). The browser checkout is createTransferCheckout/payWithTransfer from @nibgate/sdk — it broadcasts a USDC transfer to the seller’s payTo address and submits the tx hash as the x-nibgate-transfer-tx header on the access retry.

The retry must also carry an ownership proof: an EIP-191 signature (made by the paying wallet) over transferOwnershipMessage(txHash, resource) — i.e. Nibgate transfer ownership\ntx:<txHash lowercased>\nresource:<resource path or url> — sent as the x-nibgate-tx-owner header. This binds the public txHash to the payer and this specific resource. Missing proofs fail with transfer-ownership-proof-required, mismatched signers with transfer-owner-mismatch. Self-hosters can opt out with NIBGATE_TX_OWNER_PROOF_OPTIONAL=true; Nibgate-hosted surfaces additionally claim each txHash single-use per content id.

The server verifies on-chain (createTransferVerifier): the transaction mined successfully, a USDC Transfer log credits the seller’s address, and the amount is at least the resource price. On success it mints the unlock proof and returns the content, storing a paymentProvider: 'direct-transfer' receipt keyed by txHash.

import { payWithTransfer, createTransferCheckout } from '@nibgate/sdk' const result = await payWithTransfer(resource, { accessPath: '/api/nibgate/access', checkout: createTransferCheckout(resource, { // Broadcast the transfer from the connected wallet; return the txHash. sendTransfer: async ({ recipient, amount, currency }) => (await walletClient.sendTransaction({ account, to: recipient, value: 0n, data: encodeFunctionData({ abi: usdcTransferAbi, args: [recipient, parseUnits(amount, 6)] }) })) }) })

Enabling hub event reporting

After a successful x402 payment, the SDK automatically emits payment_completed and unlock_completed events to the Hub — if the creator site has these env vars set:

Env varPurpose
NIBGATE_SITE_IDHub website UUID
NIBGATE_SITE_TOKENHub website verify token
NIBGATE_API_BASEHub API URL (e.g. https://nibgate.xyz)

Without these, x402 payments still work and return unlock proofs, but unlock counts and revenue on Explore do not update.

Arc testnet receipt

Use this when the payment can link to an Arc testnet transaction hash or Arcscan URL.

Gateway balance & deposit

The hub backend exposes Circle Gateway helper status on both rails via the shared packages/internal/src/payments.js:

  • POST /api/nibshare/gateway/balance (hub) and GET /api/nibgate/gateway/balances (subblogs) — return the depositor’s testnet USDC balance (needs CIRCLE_API_KEY set on the backend; response shows { balance: "0.00 USDC" }).
  • Deposit/withdraw are handled through Circle’s Gateway API with the configured depositor/source — no custody by Nibgate.

Event payload

Unlock completion events should include:

  • amount
  • currency
  • receiver
  • recipient
  • siteId
  • contentId
  • txHash for chain transactions (this doubles as the paymentNonce for idempotent granting)
  • paymentId for gateway/provider records
  • receiptUrl when the provider returns a real receipt URL
  • chainExplorerUrl when available
  • paidAt

Earnings dashboard

The dashboard should group earnings by site, receiver, content, currency, and time range. Clicking a receipt should open the source transaction or gateway receipt when available.

Last updated on