paybondpaybond
Sign in

Settlement provider integration

Reference architecture for how Paybond integrates Stripe Connect, ACH, Coinbase x402, and Stripe MPP — control plane, webhooks, terminal settlement, and recovery.

This document is the dense reference companion to the public guide Settlement with existing payment providers. It describes how Paybond sits alongside Stripe and Coinbase without replacing them, and maps the HTTP surfaces operators and integrators touch in production.

Design principles

  1. Destinations are server-owned. Tenant settlement configuration in Postgres (paybond_tenant_settlement_config) is the source of truth for Connect account ids, Base USDC receive addresses, MPP profile ids, and Tempo session settings. Harbor snapshots resolved values onto each intent at create time (settlement.rs, settlementconfig/config.go).
  2. Funding gates are provider-scoped. Stripe payment_intent.succeeded, Coinbase authorization events, and MPP Payment Auth verification are Harbor-internal signals. They are not substitutes for payee completion evidence.
  3. Terminal settlement is evidence-driven. drive_terminal_settlement runs only from evidence_submitted, after predicate evaluation. Release vs refund is derived from the stored evaluation — not chosen by the confirm caller.
  4. Tenant isolation at every hop. Gateway webhook preflight rejects events without binding metadata or with Connect destination mismatches. Internal automation routes require tenant-scoped bearer secrets or mTLS client certs with urn:paybond:tenant:<realm> URI SAN.

Three-layer architecture

sequenceDiagram
  autonumber
  actor Admin as Tenant admin
  participant GW as Gateway
  participant PG as Postgres control plane
  participant HB as Harbor
  participant PSP as Stripe / Coinbase / Tempo
  actor App as Kit / application

  Admin->>GW: Save settlement config (console or PUT /v1/admin/settlement/config)
  GW->>PG: Immutable settlement config version

  App->>GW: POST /harbor/intents (tenant API key)
  GW->>HB: Forward create (mTLS)
  HB->>PG: Read allowed rails + destination snapshot
  HB->>PSP: Initialize funding (rail-specific)
  PSP-->>HB: Authorized / pending / challenge

  PSP->>GW: Webhook (Stripe / Coinbase Hook0)
  GW->>GW: Verify signature, enqueue paybond_webhook_job
  GW->>HB: POST /internal/v1/stripe/events or /internal/v1/coinbase/events
  HB-->>HB: Fund gate, provider state hydrate

  App->>GW: POST evidence + settlement/confirm
  HB->>PSP: Capture / void / transfer / refund (terminal)
  HB-->>GW: released or refunded

Layer responsibilities

LayerOwnsDoes not own
GatewayAuth, settlement config CRUD, webhook ingress, job queue, dispute cases, Stripe webhook-health UI dataPredicate evaluation, ledger append, PaymentIntent creation
HarborIntent state machine, funding handshake, evidence + predicate VM, drive_terminal_settlement, ledger eventsLong-lived Postgres dispute case rows
Postgres (Gateway)paybond_tenant_settlement_config, webhook job queue, paybond_stripe_webhook_eventIntent escrow rows (Harbor sled)

Per-rail reference

RailProvider touchpointFunds commitTerminal releaseTerminal refundAsync / webhook path
stripe_connectPaymentIntent on linked Connect destinationCreate-time authorization (typical)PI capturePI cancelOptional PI reconcile via recovery
stripe_ach_debitACH PaymentIntentpayment_intent.succeededTransfer / capture path on stored PIRefund on stored chargeStripe webhook → Gateway → Harbor /internal/v1/stripe/events
x402_usdc_baseCoinbase payment session (Base)/fund + authorization successCoinbase captureCoinbase voidHook0 → Gateway → Harbor /internal/v1/coinbase/events
stripe_mppPayment Auth charge (Stripe) or Tempo session/fund credential verifiedCharge: PI capture; Session: evidence-driven release after vouchersCharge: cancel/refund driver; Session: rail-specific refundCharge: payment_intent.succeeded ingest; Session: Tempo RPC recovery

stripe_connect

  • Harbor initializes a PaymentIntent against the intent-snapshotted Connect destination during create when the live Stripe runtime is enabled.
  • capability_token is often available in the create response.
  • drive_terminal_settlement with passed: true captures the held PI; passed: false cancels it.

stripe_ach_debit

  • Shares Connect destination with card rail; requires ACH debit capability ready in settlement config.
  • Intent may remain pre-funded until Stripe confirms the bank debit.
  • Webhook events of interest include payment_intent.succeeded, charge.failed, mandate.updated, and dispute/refund families — see Operations runbook §2.
  • Reconciliation (POST /internal/v1/stripe/reconcile) refreshes stored provider fields only; it does not mutate money.

x402_usdc_base

  • Amounts are USD-denominated (cents); settlement executes in USDC on Base per session authorization.
  • Gateway Coinbase webhooks use Hook0 signature verification with production timestamp tolerance 90s (configurable via PAYBOND_WEBHOOK_TIMESTAMP_TOLERANCE_SECS).
  • Harbor deduplicates via durable webhook epoch + eventId + body hash (coinbase_webhook_begin_processing).
  • After sled restore, operators must rotate Hook0 secrets and bump per-tenant webhook epoch — see runbook §2 x402 section.

stripe_mpp (Beta Preview)

  • Funding never completes on create alone; all paths go through POST /intents/{id}/fund Payment Auth.
  • Charge (intent="charge"): Stripe PI; payment_intent.succeeded webhook can complete funding if the client drops mid-handshake.
  • Session (intent="session"): Tempo deposit confirmation; session_open_pending until on-chain confirm; vouchers at POST /verify meter spend under intent budget.
  • Operational checklist: MPP hardening.

Post-funding terminal settlement

Harbor handler flow:

  1. Payee submits evidence → POST /intents/{id}/evidence → predicate VM evaluation → evidence_submitted.
  2. Caller confirms → POST /intents/{id}/settlement/confirmdrive_terminal_settlement(runtime, intent, tenant, sandbox, passed, ...).
  3. Rail driver executes capture/void/transfer/refund; intent transitions to released or refunded; ledger appends terminal events.

drive_terminal_settlement returns ConflictState if the intent is not in evidence_submitted. Idempotent if already terminal.

Dispute freeze (disputed) blocks terminal settlement until Harbor dispute/resolve — see Intent lifecycle and Disputes and evidence.

HTTP surface (Gateway and Harbor)

Tenant-authenticated (public API)

MethodPathRole
GET/v1/admin/settlement/configRead settlement config (masked for read-only principals)
PUT/v1/admin/settlement/configUpdate rails, destinations, MPP profile, x402 address
POST/harbor/intentsCreate signed intent (Gateway proxy to Harbor)
POST/harbor/intents/{id}/fundFunding handshake (x402, MPP, ACH retry)
POST/harbor/intents/{id}/evidenceSubmit payee evidence
POST/harbor/intents/{id}/settlement/confirmTerminal settlement from evaluation
GET/v1/stripe/webhook-healthStripe webhook delivery health (console Settlements)

Full schemas: Gateway OpenAPI, Harbor OpenAPI.

Provider webhook ingress (unsigned provider → Gateway)

MethodPathNotes
POST/webhooks/stripeLegacy combined endpoint
POST/webhooks/live/stripeLive environment Stripe events
POST/webhooks/sandbox/stripeSandbox environment Stripe events
POST/webhooks/coinbaseCoinbase Hook0 (x402)
POST/webhooks/live/coinbaseLive Coinbase
POST/webhooks/sandbox/coinbaseSandbox Coinbase

Verified events enqueue in paybond_webhook_job (idempotency key = provider event id) before HTTP 2xx to the provider.

Internal automation (mTLS and/or tenant-bound bearer)

MethodPathAuthPurpose
POST/internal/v1/stripe/eventsHarbor forward bearer + Stripe signature re-verifyStripe funding/settlement ingest
POST/internal/v1/stripe/reconcilePAYBOND_STRIPE_RECONCILIATION_SECRETS per tenantRead-only Stripe status refresh
GET/internal/v1/stripe/webhook-failuresInternal automation bearerBreak-glass webhook triage
POST/internal/v1/coinbase/eventsHarbor forward bearer + Hook0 metadataCoinbase x402 ingest
POST/internal/v1/coinbase/webhook-epoch/restorePAYBOND_HARBOR_INTERNAL_AUTOMATION_SECRETPost-restore replay guard

Gateway forwards webhooks using PAYBOND_HARBOR_INTERNAL_WEBHOOK_FORWARD_SECRET (with optional _PREVIOUS overlap). See Secrets and key rotation.

Stuck-intent recovery

ScenarioDetectionSafe response
ACH pendingIntent pre-funded, Stripe PI processingWait for webhook or run tenant-scoped Stripe reconcile; do not fake funded
x402 session incomplete/fund 402 loop, missing authorizationRetry canonical /fund; verify Coinbase webhook reached Gateway
MPP session_open_pendingHarbor funding status, 202 responsesTempo RPC recovery (PAYBOND_HARBOR_RECOVERY_MPP_TEMPO_RECONCILE); check PAYBOND_TEMPO_RPC_URL
Webhook missedwebhook-health failures, Harbor driftStripe reconcile or Harbor recovery tick; preserve stored provider ids
Terminal settlement retry capsettlement_last_error on intentOperator runbook §2; escalate if PI vs Harbor lifecycle mismatch persists

Harbor background recovery (when env flags enabled):

  • PAYBOND_HARBOR_RECOVERY_STRIPE_RECONCILE — retrieve Stripe status for non-terminal intents with stored PI ids.
  • Coinbase session polling for non-terminal x402 intents with payment_session_id.
  • MPP charge recovery via Stripe PI retrieve; session via Tempo deposit poll.

Recovery updates local status and safe provider fields only — no capture, transfer, or refund from reconcile alone.

Sandbox vs production

ModeSettlement runtimeWebhooks
Sandbox tenantSimulator rails; optional Stripe test-mode MPPEnvironment-scoped webhook URLs; simulator events
ProductionLive Stripe, Coinbase CDP, Tempo RPC for MPP sessionLive webhook secrets; 90s Hook0 tolerance in hosted config

PAYBOND_SETTLEMENT_MODE=simulated is for local engineering only. Hosted sandbox uses Stripe test-mode and simulator routing per tenant environment.