API reference/Transactions

Core resource

Transactions

A B2bTransaction is a conditional transfer that holds until a release condition is met. The API covers its full lifecycle: create, list, retrieve, cancel, release, and inspect the event log.

Lifecycle

Every transaction moves through an explicit state machine. Terminal states (released, refunded, cancelled, expired) cannot transition further.

State transitions
draft
  ├─ pending_participant (access-link minted)
  │    ├─ pending_funding (participant engaged)
  │    │    ├─ funded (Stripe webhook confirmed)
  │    │    │    ├─ released   (terminal)
  │    │    │    └─ refunded   (terminal)
  │    │    ├─ expired         (terminal)
  │    │    └─ cancelled       (terminal)
  │    ├─ expired              (terminal)
  │    └─ cancelled            (terminal)
  └─ cancelled                 (terminal)

Create a transaction

POST/api/v1/b2b/transactions

Scope: transactions:write. Idempotent per (account, external_reference) — repeating the same request returns the original resource with an Idempotent-Replayed: 1 response header.

Request bodyjson
{
  "external_reference": "invoice-12345",
  "amount": 50000,
  "currency": "usd",
  "description": "Milestone 1 — website redesign",
  "payer":        { "email": "customer@example.com", "name": "Customer Co." },
  "beneficiary":  { "email": "vendor@example.com", "stripe_connected_account_id": "acct_…" },
  "condition_type": "manual_approval",
  "release_method": "manual",
  "expires_at": "2026-05-30T00:00:00Z",
  "metadata": { "project_id": "abc123" }
}

Response 201

Created transactionjson
{
  "id": "b2btxn_…",
  "status": "draft",
  "external_reference": "invoice-12345",
  "amount": 50000,
  "currency": "usd",
  "beneficiary": { "stripe_connected_account_id": "acct_…", … },
  "condition_type": "manual_approval",
  "release_method": "manual",
  "created_at": "…"
}

List transactions

GET/api/v1/b2b/transactions

Scope: transactions:read. Cursor pagination on starting_after, newest-first. Filters: status, external_reference, created_after, created_before. Default limit 10, max 100.

Retrieve a transaction

GET/api/v1/b2b/transactions/:id

Scope: transactions:read. Returns the full resource including current state, participant info, and funding/release timestamps.

Cancel or release

ActionEndpointFrom stateTo stateNotes
CancelPOST /transactions/:id/cancelany non-terminalcancelledIdempotent on cancelled → cancelled
ReleasePOST /transactions/:id/releasefundedreleasedStripe payout first, then DB — on partial success a RecoveryTask is written
Release ordering is critical. Holdyn calls Stripe before updating the local DB. If the network drops between the two, a RecoveryTask reconciles on the next tick — the caller sees a 202 and an idempotent retry completes the transition.

Event log

GET/api/v1/b2b/transactions/:id/events

Every state change and access-token mutation writes an immutable event. See Events for the type catalogue.