PineTree API
Developer Infrastructure
Cloud based infrastructure that connects supported providers, payment methods, and transaction data through one developer-friendly platform.
Webhooks & Events
Receive standardized payment events as transactions move through the PineTree lifecycle.
One API for Modern Payments
PineTree gives developers a consistent way to create payment sessions, connect supported providers, and track transactions across cards and digital payment rails.
One integration
Create payments through one consistent interface instead of rebuilding checkout logic for every supported provider.
Standardized transaction states
Work with a unified transaction model across cards, cryptocurrency, hosted checkout, and in-person payments.
Real-time payment events
Keep applications synchronized with transaction updates, payment status changes, provider responses, and webhook events through PineTree’s unified API.
PineTree API
Built to power the payments behind your product
PineTree API gives developers a consistent way to create payment sessions, launch hosted or custom checkout flows, track transactions through standardized lifecycle states, and return clear payment results to their application or merchant system. PineTree coordinates the payment experience across supported providers and payment rails while your product retains control of the customer journey and surrounding business logic.
PineTree API helps developers create payments, connect supported providers, and track transaction updates through one consistent integration.
Payment Session API
- Create payment sessions from your application
- Pass amounts, currencies, references, and return URLs
- Launch hosted checkout, payment links, or custom flows
- Retrieve structured transaction results through one API
Provider Abstraction
- Connect supported providers through one integration layer
- Use a consistent request and response structure
- Avoid rebuilding payment logic for every provider
- Expand payment capabilities without changing your core flow
Standardized Events
- Receive real-time updates through PineTree webhooks
- Build around consistent transaction lifecycle states
- Synchronize orders, receipts, dashboards, and fulfillment
- Handle confirmed, failed, expired, and incomplete payments
Reliable Integration Controls
- Use merchant-scoped credentials and transaction records
- Protect duplicate requests with idempotent operations
- Keep provider credentials and private routing logic isolated
- Build for websites, platforms, marketplaces, and applications
Developer Documentation
PineTree API lets developers create checkout sessions, retrieve payment status, receive signed webhook events, and reconcile payments across enabled payment rails.
PineTree API uses versioned REST endpoints. The current API path prefix is /api/v1.
At a Glance
Supported Rails
| Rail | Assets | Availability |
|---|---|---|
solana | SOL, USDC | Live |
base | ETH, USDC | Live |
bitcoin_lightning | BTC | Live |
shift4 | Card / USD | Enabled merchants |
stripe | Card / USD | Early access |
fluidpay | Card / USD | Early access |
See Providers, Rails & Assets for what backs each rail and current availability.
Core Flow
The standard integration follows these steps:
- Create a Checkout Session from your server using a secret API key.
- Redirect the customer to the
checkoutUrlreturned in the response. - The customer completes payment on an enabled rail.
- PineTree sends a signed
payment.confirmedwebhook to your endpoint. - Verify the webhook signature, deduplicate on
eventId, then fulfill the order.
payment.confirmed (API status: "paid"). Never fulfill on the customer redirect alone, or while a payment is Processing, Failed, Expired, or Incomplete.
Quickstart
Create a checkout session server-side, redirect the customer, then fulfill only after a confirmed webhook.
Step 1 — Install the Node SDK
npm install @pinetreepayments/nodeThen create an API key in your PineTree dashboard under Developer → API Keys. Copy the full pt_live_* key — it is only shown once.
Step 2 — Create a Checkout Session
Call this from your server. Never from a browser.
import { PineTree } from "@pinetreepayments/node"
const pinetree = new PineTree(process.env.PINETREE_API_KEY)
const session = await pinetree.checkout.sessions.create({
amount: 49.99,
currency: "USD",
reference: "order_1042",
successUrl: "https://yoursite.com/paid",
cancelUrl: "https://yoursite.com/cancel"
})
res.redirect(session.checkoutUrl)Step 3 — Configure Your Webhook Endpoint
In your PineTree dashboard, go to Developer → Webhooks. Add your HTTPS endpoint URL and copy the signing secret. The endpoint must be publicly reachable and return a 2xx response after durable processing.
Step 4 — Verify and Fulfill
app.post("/webhooks/pinetree", express.raw({ type: "application/json" }), (req, res) => {
const event = pinetree.webhooks.constructEvent(
req.body,
req.headers,
process.env.PINETREE_WEBHOOK_SECRET
)
if (event.type === "payment.confirmed") {
fulfillOrder(event.data.object.reference)
}
res.json({ received: true })
})Authentication
Use secret keys on your server. Use browser keys only for public checkout creation. Never expose secret API keys in frontend code.
Key Types
| Credential | Prefix | Where to Use |
|---|---|---|
| Secret API key | pt_live_* | Server REST API calls and backend SDK usage. |
| Public browser key | pk_live_* | Browser checkout creation only. |
pt_live_* keys in frontend JavaScript, browser code, mobile apps, or public repositories. Treat secret keys like passwords. Revoke any key that may have been exposed.
Required Headers (Server)
Authorization: Bearer pt_live_your_api_key_here
Content-Type: application/jsonOptional Header
Idempotency-Key: order_1042The Idempotency-Key header is optional but strongly recommended on POST /api/v1/checkout/sessions. See Idempotency for details.
Browser Checkout Header
When creating checkout sessions from browser code, use a public key instead of a secret key:
X-PineTree-Public-Key: pk_live_your_public_key_here
Content-Type: application/jsonPublic keys (pk_live_*) are created and revoked from Developer → API Keys → Public Keys in your PineTree dashboard, the same way secret keys are managed.
Invalid Credentials
| Scenario | HTTP status | Error code |
|---|---|---|
| No Authorization header | 401 | missing_api_key |
| Key not found, revoked, or wrong format | 401 | invalid_api_key |
No X-PineTree-Public-Key header on a browser request | 401 | missing_public_key |
| Public key not found, revoked, or wrong format | 401 | invalid_public_key |
| Key lacks required permission | 403 | missing_permission |
PineTree issues pt_live_* keys only. There is no pt_test_* variant. All keys operate against live payment infrastructure.
API Keys
Create permission-limited keys from the Developer area. Use the smallest permission set required for each integration. Secret keys are only shown once at creation time.
Secret Keys
Secret keys (pt_live_*) authenticate server-to-server REST requests. Store them in environment variables, not in source code or configuration files that may be committed to version control.
# .env
PINETREE_API_KEY=pt_live_a1b2c3d4e5f6...
PINETREE_WEBHOOK_SECRET=your_webhook_signing_secretPublic Browser Keys
Public keys (pk_live_*) are safe to embed in browser JavaScript. They can only create checkout sessions and cannot access payments, webhook deliveries, or any private merchant data.
Permissions
| Permission | Grants |
|---|---|
checkout.sessions:create | Create Checkout Sessions via POST /api/v1/checkout/sessions. Also accepted in place of checkout.sessions:read/write for back-compat. |
checkout.sessions:read | List and retrieve Checkout Sessions. |
checkout.sessions:write | Cancel or expire Checkout Sessions. |
payments:read | Retrieve Payment objects. |
checkout.links:create | Reserved for payment-link creation. Granted by default today but not yet enforced by any live endpoint. |
webhooks:read | List Webhook Deliveries. |
webhooks:write | Retry Webhook Deliveries. |
Best Practices
- Grant only the permissions required for the specific integration.
- Use separate keys for different services or environments.
- Revoke any key that may have been accidentally exposed.
- Monitor Last Used timestamps in the dashboard to identify stale keys.
- Store secret keys in a secrets manager or environment variables — never in code.
Checkout Sessions
Checkout Sessions create hosted payment pages. Create sessions from your server and fulfill orders only after confirmed webhook events.
checkoutUrl. Do not construct or parse this URL — treat it as an opaque redirect destination.
Create a Session
Creates a hosted Checkout Session. Requires permission checkout.sessions:create
Request body
{
"amount": 49.99, // required — major currency units. 49.99 means $49.99
"currency": "USD", // optional — defaults to "USD"
"reference": "order_1042", // optional — your order/cart ID
"customer": {
"email": "jane@example.com" // optional
},
"successUrl": "https://yoursite.com/paid",
"cancelUrl": "https://yoursite.com/cancel",
"metadata": { "cartId": "cart_123" },
"rails": ["solana", "base"] // optional — limit available payment methods
}Response
{
"id": "3f9a1c2e-4b6d-4a11-9c2f-8e1a6d2b7f40",
"object": "checkout.session",
"status": "open",
"amount": 49.99,
"currency": "USD",
"reference": "order_1042",
"customer": { "email": "jane@example.com" },
"metadata": { "cartId": "cart_123" },
"checkoutUrl": "https://app.pinetree-payments.com/checkout/8f2a1b9c3d4e5f60",
"paymentId": null,
"supportedRails": ["solana", "base", "bitcoin_lightning"],
"successUrl": "https://yoursite.com/paid",
"cancelUrl": "https://yoursite.com/cancel",
"createdAt": "2026-06-22T12:00:00.000Z",
"expiresAt": "2026-06-23T12:00:00.000Z"
}id on Checkout Sessions and Payments is a plain UUID with no prefix — do not write validation or routing logic that assumes a cs_ or pay_ prefix. Webhook event IDs use an evt_ prefix — see Webhook Payload.
| Error code | Meaning |
|---|---|
missing_api_key | No Authorization header. |
missing_permission | Key lacks checkout.sessions:create. |
invalid_amount | amount missing, zero, or not a positive number. |
invalid_json | Request body is not valid JSON. |
invalid_rails | Unrecognized rail value in rails array. |
invalid_customer | Malformed customer object. |
invalid_metadata | Malformed metadata object. |
invalid_url | Malformed successUrl or cancelUrl. |
idempotency_key_conflict | Same Idempotency-Key, different body. |
idempotency_request_in_progress | Request with this key already in flight. |
List Sessions
Returns a paginated list of Checkout Sessions. Requires permission checkout.sessions:read (or checkout.sessions:create)
| Query parameter | Type | Description |
|---|---|---|
limit | integer | 1–100. Defaults to 10. |
status | string | One of open, processing, paid, failed, expired, canceled. |
cursor | string | Pagination cursor from a previous response's nextCursor. Also accepted as starting_after. |
reference | string | Filter by merchant reference. |
created_after | ISO 8601 | Include sessions created after this date. |
created_before | ISO 8601 | Include sessions created before this date. |
// List response envelope
{
"object": "list",
"data": [ /* checkout.session objects */ ],
"hasMore": true,
"nextCursor": "eyJjcmVhdGVkQXQiOi..."
}| Error code | Meaning |
|---|---|
invalid_filter | limit out of range. |
unsupported_status | Unrecognized status filter value. |
invalid_cursor | Malformed pagination cursor. |
Retrieve a Session
Retrieves a single Checkout Session. Requires permission checkout.sessions:read or checkout.sessions:create
Cancel a Session
Cancels an open Checkout Session. Requires permission checkout.sessions:write (or checkout.sessions:create). Returns checkout_session_not_cancelable if the session is not in a cancelable state.
Expire a Session
Immediately expires an open Checkout Session. Requires permission checkout.sessions:write (or checkout.sessions:create). Returns checkout_session_not_expirable if the session is not in an expirable state.
Session Statuses
| Status | Description |
|---|---|
open | Waiting for customer payment |
processing | Payment broadcast; awaiting confirmation |
paid | Confirmed — fulfill the order |
failed | Payment failed |
expired | Session expired without payment |
canceled | Explicitly canceled by merchant |
Sessions that are not explicitly canceled or expired will expire automatically after 24 hours.
Browser Checkout
Create checkout sessions from browser code using a public key. Public keys are safe to embed in frontend JavaScript and cannot access private merchant data.
pk_live_* key can only create checkout sessions. It cannot retrieve payments, list webhook deliveries, or perform any back-office operation.
Browser Checkout Endpoint
Creates a Checkout Session using a public browser key. Authentication: X-PineTree-Public-Key header.
| Error code | Meaning |
|---|---|
missing_public_key | No X-PineTree-Public-Key header. |
invalid_public_key | Public key not found, revoked, or wrong format. |
invalid_amount | amount missing, zero, or not a positive number. |
invalid_json | Request body is not valid JSON. |
invalid_rails | Unrecognized rail value in rails array. |
Raw Fetch Example
const response = await fetch(
"https://app.pinetree-payments.com/api/v1/browser/checkout/sessions",
{
method: "POST",
headers: {
"X-PineTree-Public-Key": "pk_live_your_public_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 49.99,
currency: "USD",
reference: "order_1042",
successUrl: window.location.origin + "/paid",
cancelUrl: window.location.origin + "/cancel",
}),
}
)
const session = await response.json()
window.location.href = session.checkoutUrlJavaScript SDK
npm install @pinetreepayments/jsimport { PineTreeJS } from "@pinetreepayments/js"
const ptjs = new PineTreeJS("pk_live_your_public_key_here")
const session = await ptjs.checkout.createSession({
amount: 49.99,
currency: "USD",
reference: "order_1042",
successUrl: window.location.origin + "/paid",
cancelUrl: window.location.origin + "/cancel",
})
ptjs.checkout.open(session)React SDK
npm install @pinetreepayments/reactimport { PineTreeProvider, PineTreeCheckoutButton } from "@pinetreepayments/react"
function App() {
return (
<PineTreeProvider publicKey="pk_live_your_public_key_here">
<CheckoutPage />
</PineTreeProvider>
)
}
function CheckoutPage() {
return (
<PineTreeCheckoutButton
amount={49.99}
currency="USD"
reference="order_1042"
successUrl={window.location.origin + "/paid"}
cancelUrl={window.location.origin + "/cancel"}
>
Pay with Crypto
</PineTreeCheckoutButton>
)
}Payments
Use Payments for reconciliation, status lookup, and back-office workflows. Use payment.confirmed webhooks — not polling — for order fulfillment.
Retrieve a Payment
Retrieves a Payment object by ID. Requires permission payments:read. There is no list-payments endpoint today — retrieve payments by ID only.
Payment Object Fields
| Field | Type | Description |
|---|---|---|
id | string | PineTree payment ID. A plain UUID — no prefix. |
object | string | Always "payment". |
status | string | Current payment status. See status mapping below. |
amount | number | Amount in major currency units (USD). |
currency | string | Fiat currency, e.g. "USD". |
rail | string | Payment rail, e.g. "solana", "base", "bitcoin_lightning". |
network | string | Same value as rail, returned alongside it for compatibility with webhook payloads. |
reference | string | Your merchant reference / order ID. |
createdAt | string | ISO 8601 creation timestamp. |
updatedAt | string | ISO 8601 last-update timestamp. |
metadata | object | Public metadata from session creation. |
asset, checkoutSessionId, or confirmedAt on the REST object
The Payment object does not currently return a separate asset field (e.g. distinguishing SOL vs. USDC on the same rail), a checkoutSessionId back-reference, or a confirmedAt timestamp. confirmedAt is available on the webhook payload only.
"status": "paid" when the payment is confirmed. The visible customer-facing state is called Confirmed. Do not check for status === "confirmed" — confirmed is never a real API status value, only paid is. Only fulfill orders after payment.confirmed (status: "paid"); never fulfill on the customer redirect alone, or while Processing, Failed, Expired, or Incomplete.
Payment Object
{
"id": "3f9a1c2e-4b6d-4a11-9c2f-8e1a6d2b7f40",
"object": "payment",
"status": "paid",
"amount": 49.99,
"currency": "USD",
"rail": "solana",
"network": "solana",
"reference": "order_1042",
"metadata": {},
"createdAt": "2026-06-22T17:59:30.000Z",
"updatedAt": "2026-06-22T18:00:05.000Z"
}Status Mapping
| Visible state | API status value | Webhook event | Terminal |
|---|---|---|---|
| Waiting | open / pending | payment.pending | No |
| Processing | processing | payment.processing | No |
| Confirmed | paid | payment.confirmed | Yes |
| Failed | failed | payment.failed | Yes |
| Expired | expired | payment.expired | Yes |
| Incomplete | incomplete | payment.incomplete | Yes |
Session Statuses
Checkout Session status tracks the aggregate state of the session, which may span multiple payment attempts. Session status and Payment status use similar but not identical labels.
| Status | Meaning | Terminal |
|---|---|---|
open | Session created and the checkout page is waiting for the customer to begin payment. | No |
processing | Customer has submitted payment; PineTree is awaiting on-chain or provider confirmation. | No |
paid | Payment confirmed. Fulfill the order on the corresponding payment.confirmed event. | Yes |
failed | Payment attempt failed. Customer may need to retry if a new session is created. | Yes |
expired | Session expired without a confirmed payment. Sessions expire automatically after 24 hours. | Yes |
canceled | Session was explicitly canceled by the merchant via the API or dashboard. | Yes |
paid is the checkout.session.completed or payment.confirmed webhook event — not polling the session status. Rely on webhooks for fulfillment; use session retrieval for UI state only.
Providers, Rails & Assets
Rails are payment paths. Assets are what the customer pays on that path.
Supported Rails
| Rail | Assets / Currency | Description |
|---|---|---|
solana | SOL, USDC | Solana Pay payments. |
base | ETH, USDC | Base network payments. |
bitcoin_lightning | BTC | Lightning invoice payments. |
shift4 | Card / USD | Card payments for approved merchants. Contact support to enable. |
Card processing via Shift4 is available to approved merchants through the PineTree merchant onboarding flow. Contact info@pinetree-payments.com to apply. Additional card processors are available in early access.
Provider vs. Rail vs. Asset
A provider is the company powering payments — the external payment platform or processor that moves the money (Shift4, Stripe, FluidPay, Speed). A rail is the payment network — the path a Checkout Session travels on (solana, base, bitcoin_lightning, shift4, stripe, fluidpay). An asset is the currency/token the customer actually pays with (SOL, USDC, ETH, BTC, USD).
solana and base are native PineTree rails: there is no separate third-party processor behind them, so the rail identifier is the provider, and both are live today. bitcoin_lightning, shift4, stripe, and fluidpay each have a distinct backing provider — see the table below for current availability.
Providers
| Provider | Rail | Assets / Currency | Availability | Notes |
|---|---|---|---|---|
| Solana Pay | solana | SOL, USDC | Live | Native Solana rail. Not a third-party processor. |
| Base Pay | base | ETH, USDC | Live | Native Base rail. Not a third-party processor. Coinbase Wallet is one supported wallet on this rail — it is not the provider. |
| Speed | bitcoin_lightning | BTC | Live | Speed powers Lightning settlement. Requires either a merchant-connected Speed account or PineTree's platform-level Lightning settlement to be enabled for your environment. Lightning via Nostr Wallet Connect (NWC) is also supported. |
| Shift4 | shift4 | Card / USD | Enabled merchants | Card processing available to approved merchants through PineTree's onboarding flow. |
| Stripe | stripe | Card / USD | Early access | Available in early access through PineTree's Stripe Connect integration (Connect, Payments, and Terminal). Availability depends on merchant onboarding and which capabilities are enabled on the connected account. Contact support to begin onboarding. |
| FluidPay | fluidpay | Card / USD | Early access | Available in early access. Merchant onboarding and provider approval are required before processing is enabled. Contact support for current status. |
Rail and Asset by Payment Type
| Payment | rail | Display label |
|---|---|---|
| Solana SOL | solana | Solana Pay · SOL |
| Solana USDC | solana | Solana Pay · USDC |
| Base ETH | base | Base Pay · ETH |
| Base USDC | base | Base Pay · USDC |
| Bitcoin Lightning | bitcoin_lightning | Lightning · BTC |
| Card (Shift4) | shift4 | Card · USD |
asset field
rail (called network in webhook payloads) identifies the payment path, such as solana or base. There is currently no separate asset field to distinguish which token was paid on a multi-asset rail — for example, SOL vs. USDC on Solana, or ETH vs. USDC on Base. Do not build logic that expects one; use the rails table above as the source of truth for which assets a rail can settle.
Restricting Rails on a Session
Pass the optional rails array when creating a session to limit which payment methods are offered on the hosted checkout page:
{
"amount": 49.99,
"currency": "USD",
"rails": ["solana", "base"] // only show Solana and Base options
}Payment States
Confirmed is the positive visible payment state and the only state that should trigger order fulfillment. All other terminal states — Failed, Expired, Incomplete — must never trigger fulfillment.
| Visible state | API status | Webhook event | Terminal | Fulfill? |
|---|---|---|---|---|
| Waiting | open | payment.pending | No | No |
| Processing | processing | payment.processing | No | No |
| Confirmed | paid | payment.confirmed | Yes | Yes ✓ |
| Failed | failed | payment.failed | Yes | No |
| Expired | expired | payment.expired | Yes | No |
| Incomplete | incomplete | payment.incomplete | Yes | No |
payment.confirmed (API status: "paid"). Never fulfill on the customer redirect alone, or while a payment is Processing, Failed, Expired, or Incomplete. The visible state Confirmed maps to status: "paid" in the API Payment object — do not check status === "confirmed", it will not match.
State Lifecycle
CREATED → PENDING → PROCESSING → CONFIRMED (status: "paid")
└→ FAILED (status: "failed")
└→ INCOMPLETE (status: "incomplete")
CREATED → EXPIRED (status: "expired")Webhooks
Webhook deliveries are signed with PineTree headers. Verify the raw body before processing.
Setup
- Go to Developer → Webhooks in your PineTree dashboard.
- Enter your HTTPS endpoint URL. HTTP endpoints are not accepted.
- Copy the signing secret shown on the page. It is only shown once.
- Store the secret as
PINETREE_WEBHOOK_SECRETin your environment.
Webhook Headers
| Header | Description |
|---|---|
PineTree-Signature | HMAC-SHA256 hex signature. Verify this before processing. |
PineTree-Timestamp | ISO 8601 timestamp used in the signature. PineTree rejects events older than 5 minutes. |
PineTree-Event-Id | Unique event ID. Store and use for deduplication. |
PineTree-Event-Schema | Always payments-v1. |
PineTree-Webhook-Version | Legacy compatibility header carrying the same schema value. Canonical header is PineTree-Event-Schema. |
Signature Formula
PineTree signs each delivery with HMAC-SHA256 over the timestamp and the raw request body:
signed_payload = PineTree-Timestamp + "." + raw_request_body
expected_sig = HMAC_SHA256(webhook_secret, signed_payload)
header_sig = PineTree-Signature header value
// Verified if: constant_time_equals(expected_sig, header_sig)
// and: abs(now - PineTree-Timestamp) < 300 secondsVerification — Node SDK
// CRITICAL: Use express.raw() — do NOT parse the body before this call
app.post(
"/webhooks/pinetree",
express.raw({ type: "application/json" }),
(req, res) => {
let event
try {
event = pinetree.webhooks.constructEvent(
req.body,
req.headers,
process.env.PINETREE_WEBHOOK_SECRET
)
} catch (err) {
return res.status(400).send("Signature verification failed")
}
const alreadySeen = await checkAndStoreEventId(event.eventId)
if (alreadySeen) return res.json({ received: true })
if (event.type === "payment.confirmed") {
await fulfillOrder(event.data.object.reference)
}
res.json({ received: true })
}
)Reliability Rules
- PineTree delivers events at least once. Your endpoint must handle duplicate events idempotently.
- Return a
2xxresponse only after writing event data to durable storage. - If your endpoint returns a non-2xx, PineTree will retry with backoff — see Webhook Deliveries.
- Do not fulfill orders based on the customer redirect URL alone. Always wait for
payment.confirmed.
Webhook Payload
Every PineTree webhook is delivered as a JSON object with a standard payments-v1 envelope. The event type, schema, and event ID are at the top level. The payment or session object is inside data.object.
Event Envelope
{
"eventId": "evt_01abc...", // unique — store for deduplication
"object": "event",
"type": "payment.confirmed",
"schema": "payments-v1",
"createdAt": "2026-06-22T18:00:00.000Z",
"livemode": true,
"data": {
"object": { /* payment or checkout.session object */ }
}
}Payment Event Payload
{
"eventId": "evt_01abc...",
"object": "event",
"type": "payment.confirmed",
"schema": "payments-v1",
"createdAt": "2026-06-22T18:00:00.000Z",
"livemode": true,
"data": {
"object": {
"id": "3f9a1c2e-4b6d-4a11-9c2f-8e1a6d2b7f40",
"object": "payment",
"status": "paid",
"network": "solana",
"amount": 49.99,
"currency": "USD",
"reference": "order_1042",
"confirmedAt": "2026-06-22T18:00:05.000Z",
"metadata": { "cartId": "cart_123" }
}
}
}network to identify the payment rail (e.g., "solana", "base"). The REST API Payment object uses rail for the same value (and also returns network alongside it). Use event.data.object.network in webhook handlers.
Checkout Session Event Payload
{
"eventId": "evt_02def...",
"object": "event",
"type": "checkout.session.completed",
"schema": "payments-v1",
"createdAt": "2026-06-22T18:00:05.000Z",
"livemode": true,
"data": {
"object": {
"id": "3f9a1c2e-4b6d-4a11-9c2f-8e1a6d2b7f40",
"object": "checkout.session",
"status": "paid",
"amount": 49.99,
"currency": "USD",
"reference": "order_1042",
"customer": { "email": "jane@example.com" },
"metadata": { "cartId": "cart_123" },
"checkoutUrl": "https://app.pinetree-payments.com/checkout/8f2a1b9c3d4e5f60",
"paymentId": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
"supportedRails": ["solana", "base", "bitcoin_lightning"],
"successUrl": "https://yoursite.com/paid",
"cancelUrl": "https://yoursite.com/cancel",
"createdAt": "2026-06-22T17:59:00.000Z",
"expiresAt": "2026-06-23T17:59:00.000Z"
}
}
}Payment Event Object Fields
| Field | Type | Description |
|---|---|---|
id | string | Payment ID. A plain UUID — no prefix. |
object | string | Always "payment". |
status | string | Payment status at time of event. |
network | string | Rail identifier (e.g., solana, base). |
amount | number | Amount in major currency units. |
currency | string | Fiat currency. |
reference | string | Your merchant reference / order ID. |
confirmedAt | string | ISO 8601 confirmation time. Present on payment.confirmed. |
metadata | object | Public metadata from session creation. |
Webhook Events
Implemented merchant webhook events in the payments-v1 schema. Use eventId to deduplicate — PineTree delivers at least once.
Payment Events
| Event | When it fires | Fulfill? |
|---|---|---|
payment.created | Payment object first created. | No |
payment.pending | Waiting for customer or network action. | No |
payment.processing | Payment detected, awaiting confirmation. | No |
payment.confirmed | Payment fully confirmed. Primary fulfillment trigger. | Yes ✓ |
payment.failed | Payment failed. | No |
payment.expired | Payment timed out. | No |
payment.canceled | Payment was canceled. | No |
payment.incomplete | Customer abandoned or no funds were sent. | No |
payment.refunded | Payment was refunded. | No |
Checkout Session Events
| Event | When it fires |
|---|---|
checkout.session.created | Session created via POST /api/v1/checkout/sessions. |
checkout.session.processing | Session has a payment in processing state. |
checkout.session.completed | Session payment confirmed. Object carries the full checkout session. |
checkout.session.failed | Session payment failed. |
checkout.session.expired | Session expired after 24 hours without confirmed payment. |
checkout.session.canceled | Session canceled by merchant. |
Payment Link Events
| Event | When it fires |
|---|---|
payment_link.created | Payment link created. |
payment_link.disabled | Payment link disabled or deactivated. |
payment_link.expired | Payment link expired. |
Legacy Events
| Legacy event | Normalizes to |
|---|---|
checkout.session.paid | checkout.session.completed |
payment.cancelled | payment.canceled |
Legacy event aliases from prior API versions are accepted and normalized to their canonical equivalents. Use the canonical event names listed above for all new integrations.
Webhook Deliveries
Use Webhook Deliveries to inspect delivery history, debug failed events, and manually trigger retries from your server tooling or automation.
Lists Webhook Deliveries. Requires permission webhooks:read
| Query parameter | Description |
|---|---|
limit | 1–100. Defaults to 20. |
status | One of pending, delivered, failed, dead_letter. |
eventType | Filter by event type, e.g. payment.confirmed. |
cursor | Pagination cursor from a previous response's nextCursor. |
// List response envelope
{
"object": "list",
"data": [ /* webhook.delivery objects */ ],
"hasMore": false,
"nextCursor": null
}Manually retries a failed or dead-letter delivery. Requires permission webhooks:write
// Node SDK — manual retry
const delivery = await pinetree.webhookDeliveries.retry("6c7d8e9f-0a1b-4c2d-9e3f-4a5b6c7d8e9f")
console.log(delivery.status) // "delivered" if retry succeeded
console.log(delivery.attemptCount) // total attempts including this retryWebhook Delivery Object
{
"id": "6c7d8e9f-0a1b-4c2d-9e3f-4a5b6c7d8e9f",
"object": "webhook.delivery",
"eventType": "payment.confirmed",
"status": "delivered",
"attemptCount": 1,
"nextAttemptAt": null,
"lastAttemptAt": "2026-06-22T18:00:06.000Z",
"lastStatusCode": 200,
"lastError": null,
"deliveredAt": "2026-06-22T18:00:06.000Z",
"deadLetteredAt": null,
"createdAt": "2026-06-22T18:00:00.000Z"
}Delivery Statuses
| Status | Meaning |
|---|---|
pending | Queued for initial delivery or scheduled retry. |
delivered | Your endpoint returned a 2xx response. |
failed | Last delivery attempt failed; retry is scheduled. |
dead_letter | All retry attempts exhausted. Requires manual retry or operator intervention. |
Automatic Retry Schedule
PineTree retries failed deliveries with backoff. After 10 failed attempts the delivery moves to dead_letter.
| Attempt | Delay before retry |
|---|---|
| 1 | 60 seconds |
| 2 | 120 seconds |
| 3 | 240 seconds |
| 4 | 480 seconds |
| 5 | 960 seconds |
| 6 | 1,800 seconds (30 min) |
| 7–10 | 3,600 seconds (1 hour) |
| After 10 | dead_letter — no further automatic retries |
Use POST /api/v1/webhook-deliveries/{id}/retry to manually retry a dead_letter delivery after fixing the issue on your endpoint.
Errors
All errors return a consistent JSON structure with a type, code, message, and requestId. Include the requestId when contacting support.
Error Response Shape
{
"error": {
"type": "authentication_error",
"code": "missing_api_key",
"message": "A PineTree API key is required.",
"requestId": "3f9a1c2e-4b6d-4a11-9c2f-8e1a6d2b7f40"
}
}Error Types
| Type | HTTP status | Meaning |
|---|---|---|
authentication_error | 401 | Missing or invalid API key. |
authorization_error | 403 | Key lacks a required permission. |
invalid_request_error | 400 | Malformed request or invalid field value. |
idempotency_error | 409 | Idempotency key conflict or request in progress. |
not_found_error | 404 | Resource does not exist or is not accessible. |
api_error | 500 | PineTree service error. Safe to retry with backoff. |
Common Error Codes
| Code | Type | Meaning |
|---|---|---|
missing_api_key | authentication_error | No Authorization header provided. |
invalid_api_key | authentication_error | Key not found, revoked, or wrong format. |
missing_public_key | authentication_error | No X-PineTree-Public-Key header on a browser request. |
invalid_public_key | authentication_error | The public key value is invalid or revoked. |
missing_permission | authorization_error | Key does not have the required permission for this endpoint. |
invalid_amount | invalid_request_error | amount is missing, zero, or not a positive number. |
invalid_json | invalid_request_error | Request body is not valid JSON. |
invalid_rails | invalid_request_error | Unrecognized rail value in the rails array. |
invalid_customer | invalid_request_error | Malformed customer object. |
invalid_metadata | invalid_request_error | Malformed metadata object. |
invalid_url | invalid_request_error | Malformed successUrl or cancelUrl. |
invalid_filter | invalid_request_error | limit out of range on a list request. |
unsupported_status | invalid_request_error | Unrecognized status filter value. |
invalid_cursor | invalid_request_error | Malformed pagination cursor. |
missing_session_id | invalid_request_error | No session ID in the request path. |
checkout_session_not_cancelable | invalid_request_error | Session is not in a cancelable state. |
checkout_session_not_expirable | invalid_request_error | Session is not in an expirable state. |
checkout_session_not_found | not_found_error | No session found for the provided ID. |
payment_not_found | not_found_error | No payment found for the provided ID. |
webhook_delivery_not_found | not_found_error | No webhook delivery found for the provided ID. |
idempotency_key_conflict | idempotency_error | Same key was used with a different request body. |
idempotency_request_in_progress | idempotency_error | A request with this key is still in flight. |
idempotency_storage_failure | api_error | PineTree failed to persist idempotency state. Safe to retry. |
internal_error | api_error | Unexpected server error. Safe to retry with backoff. |
Node SDK
The Node SDK throws V1ApiError on any non-2xx response, carrying the same type, code, and requestId shown above. Check error.type/error.code to branch your handling.
Idempotency
Add an Idempotency-Key header to POST /api/v1/checkout/sessions to safely retry the request without creating duplicate sessions. The key is optional but strongly recommended for any production integration.
How It Works
| Scenario | Result |
|---|---|
| Same key + same request body | Returns the original session. No duplicate is created. |
| Same key + different request body | 409 — idempotency_key_conflict. Do not retry. |
| Request still in flight | 409 — idempotency_request_in_progress. Wait and retry. |
REST Example
POST /api/v1/checkout/sessions HTTP/1.1
Authorization: Bearer pt_live_...
Content-Type: application/json
Idempotency-Key: order_1042 ← use your stable order ID
{
"amount": 49.99,
"currency": "USD",
"reference": "order_1042"
}Node SDK Example
const session = await pinetree.checkout.sessions.create(
{
amount: 49.99,
currency: "USD",
reference: "order_1042",
successUrl: "https://yoursite.com/paid",
cancelUrl: "https://yoursite.com/cancel",
},
{ idempotencyKey: "order_1042" }
)Best Practices
- Use a stable, unique value per order — your order ID or cart ID is ideal.
- Do not reuse the same idempotency key for different orders.
- On network failure or timeout, retry with the same idempotency key and same body.
- If you receive a
409 idempotency_key_conflict, do not retry — generate a new session for the new request.
SDKs
PineTree provides official SDKs for Node.js (server), JavaScript (browser), and React. Use the Node SDK for all server-side operations. Use the JS and React SDKs with public browser keys only.
Node SDK — @pinetreepayments/node
npm install @pinetreepayments/nodeUse on your server with a pt_live_* secret key. Requires Node.js 18+.
import { PineTree } from "@pinetreepayments/node"
const pinetree = new PineTree(process.env.PINETREE_API_KEY)
// Checkout Sessions
const session = await pinetree.checkout.sessions.create(
{ amount: 49.99, currency: "USD", reference: "order_1042" },
{ idempotencyKey: "order_1042" }
)
await pinetree.checkout.sessions.retrieve(session.id)
await pinetree.checkout.sessions.list({ status: "paid", limit: 20 })
await pinetree.checkout.sessions.cancel(session.id)
await pinetree.checkout.sessions.expire(session.id)
// Payments — retrieve only; there is no payments.list()
const payment = await pinetree.payments.retrieve("3f9a1c2e-4b6d-4a11-9c2f-8e1a6d2b7f40")
// → payment.status === "paid" means confirmed
// Webhook Deliveries
const deliveries = await pinetree.webhookDeliveries.list({ status: "failed" })
await pinetree.webhookDeliveries.retry("6c7d8e9f-0a1b-4c2d-9e3f-4a5b6c7d8e9f")
// Verify a webhook signature
const event = pinetree.webhooks.constructEvent(
req.body, // raw Buffer — must be raw, not parsed
req.headers,
process.env.PINETREE_WEBHOOK_SECRET
)JavaScript SDK — @pinetreepayments/js
npm install @pinetreepayments/jsUse in browser environments with a pk_live_* public key. Cannot use secret keys.
import { PineTreeJS } from "@pinetreepayments/js"
const ptjs = new PineTreeJS("pk_live_your_public_key_here")
const session = await ptjs.checkout.createSession({
amount: 49.99,
currency: "USD",
reference: "order_1042",
successUrl: window.location.origin + "/paid",
cancelUrl: window.location.origin + "/cancel",
})
ptjs.checkout.open(session) // redirects to hosted checkoutReact SDK — @pinetreepayments/react
npm install @pinetreepayments/reactReact components and hooks for checkout buttons. Uses @pinetreepayments/js under the hood with a pk_live_* public key.
import { PineTreeProvider, PineTreeCheckoutButton } from "@pinetreepayments/react"
export function Providers({ children }) {
return (
<PineTreeProvider publicKey="pk_live_your_public_key_here">
{children}
</PineTreeProvider>
)
}
export function BuyButton({ orderId, amount }) {
return (
<PineTreeCheckoutButton
amount={amount}
currency="USD"
reference={orderId}
successUrl={`${window.location.origin}/paid`}
cancelUrl={`${window.location.origin}/cancel`}
>
Pay with Crypto
</PineTreeCheckoutButton>
)
}Testing
PineTree currently issues only pt_live_* keys — there is no separate PineTree sandbox or test-mode environment. Test PineTree integrations with small live amounts and an HTTPS tunnel for local webhook development.
pt_live_* keys only — there is no pt_test_* variant, and no PineTree-side sandbox. Individual payment providers behind a rail (for example Stripe) may still offer their own sandbox or test mode for testing directly with that provider; that is independent of PineTree's API, which always operates on live keys. amount uses major currency units.
Test Webhooks Locally
Use an HTTPS tunnel to expose your local server to PineTree's webhook delivery:
ngrok http 3000
# Register the HTTPS URL in Developer → Webhooks:
https://abc123.ngrok-free.app/webhooks/pinetreeIntegration Test Checklist
- Happy path — Create session → customer pays →
payment.confirmedreceived → order fulfilled. - Failed payment —
payment.failedreceived → order not fulfilled. - Expired session — Session times out →
payment.expiredreceived → order not fulfilled. - Incomplete session — Customer abandons →
payment.incompletereceived → order not fulfilled. - Duplicate delivery — Replay the same event ID → your handler skips fulfillment on the second call.
- Signature tampered — Modify the payload →
constructEventthrows → handler returns 400. - Idempotent creation — Create session twice with same
Idempotency-Key→ same session returned, no duplicate. - Webhook retry — Make your endpoint return 500 → verify retry appears in Webhook Deliveries list.
Useful Environment Variables
PINETREE_API_KEY=pt_live_a1b2c3d4...
PINETREE_WEBHOOK_SECRET=your_webhook_signing_secret
NEXT_PUBLIC_PINETREE_PUBLIC_KEY=pk_live_... # browser usageGo-Live Checklist
Complete all items before accepting real customer payments at volume.
API Keys & Credentials
- Secret API key created with the minimum required permissions.
- Secret key stored in environment variables, not in source code.
- Secret key is not present in any frontend bundle, browser console, or public repository.
- Public browser key (
pk_live_*) used for all frontend checkout flows.
Webhooks
- Webhook endpoint is HTTPS only.
- Webhook signing secret stored as
PINETREE_WEBHOOK_SECRET. PineTree-Signatureverified on every incoming event using raw body.- Body is not JSON-parsed before calling
constructEvent. - Endpoint returns
2xxafter durable write, not before. PineTree-Event-Idstored and checked to deduplicate at-least-once delivery.- Dead-letter deliveries are monitored and alerted on.
Payment Flows
- Orders are fulfilled only after
payment.confirmed. - Customer redirect to
successUrldoes not trigger fulfillment alone. payment.failed,payment.expired,payment.incompletenever trigger fulfillment.- Happy path tested end-to-end with a small live transaction.
- Failed and expired flows tested.
- Duplicate event handling verified.
Rails & Providers
- At least one payment rail is configured and verified in the merchant dashboard.
- Treasury wallet addresses are verified for each enabled crypto rail.
- If Shift4 card processing is enabled: merchant application is approved for production processing.
- If Stripe card processing is enabled (early access): Connect account is active with the capabilities your integration needs — Connect, Payments, Terminal — enabled.
- If FluidPay card processing is enabled (early access): merchant onboarding and provider approval are complete.
- Session
successUrlandcancelUrlare production HTTPS URLs.
Not Yet Supported
The following capabilities are planned for future PineTree API versions. None of these are required for the current hosted checkout and webhook integration pattern.
| Capability | Notes |
|---|---|
| Refund API | Refunds are currently processed through the merchant dashboard. A REST endpoint is planned. |
| Payout / settlement API | Settlement preferences and withdrawals are configured in the dashboard. Programmatic settlement API is planned. |
| Disputes API | Dispute management for card payments will be available once card processing is generally available. |
| Sandbox / test-mode keys | PineTree currently uses live keys only. A dedicated test environment is a planned roadmap item. |
| Advanced reporting API | Reports are available for download and email from the merchant dashboard. A REST reporting endpoint is planned. |
| Stripe card processing | In early access. Contact support for access. |
| Fluid Pay card processing | In early access. Contact support for access. |
| Recurring billing / subscriptions | One-time checkout sessions are supported. Subscription and recurring payment APIs are on the roadmap. |
| Customer objects | Customer data is currently stored as metadata on sessions and payments. A dedicated Customer API is planned. |
| Invoice API | Merchant-generated invoices are not yet available via the API. |
Support
Contact PineTree for integration help, API access, and merchant onboarding questions.
Contact
What to Include in a Support Request
Include the following in support requests: merchant account email, API endpoint, requestId from the error response, Checkout Session ID, Payment ID, webhook Event ID, error type and code, and approximate timestamp (UTC).

