PayDirect

Payments API

Create and manage payments on Base mainnet (crypto) or via Stripe Checkout (USD cards). Both rails share the same payment lifecycle, webhooks, and verify semantics.

Payment Lifecycle
Every payment moves through these statuses
StatusDescription
pending
Payment created, waiting for on-chain transfer to the workspace receiving address
detected
Incoming transfer detected on Base (not yet confirmed)
confirmed
Required block confirmations met
forwarded
Net amount sent to merchant wallet, fee sent to treasury. Settlement complete.
failed
Settlement failed (insufficient funds, chain error, etc.)
expired
No transfer received within the expiry window (default: 60 minutes)
cancelled
Merchant cancelled the payment before it was confirmed
Sandbox behavior: In sandbox mode (using pd_test_ keys), crypto payments complete instantly (pending → forwarded). Stripe payments also auto-complete without a real Checkout session.
Payment Rails
Choose crypto (default) or Stripe card checkout

Set paymentMethod on POST /api/v1/payments. Responses include paymentProvider (onchain or stripe).

RailpaymentMethodtokenSymbol
Crypto (Base)crypto (default)USDC, ETH, ADAO
Stripe CheckoutstripeUSD only

Live Stripe payments return checkoutUrl. Fulfillment is handled by POST /api/webhooks/stripe (configure STRIPE_WEBHOOK_SECRET in your deployment).

Stripe Integration
Hosted Checkout, platform webhooks, and API version alignment
Merchant backend                PayDirect                    Stripe
     |                              |                            |
     | POST /api/v1/payments        |                            |
     | (paymentMethod: stripe)      |                            |
     |----------------------------->| create payment row         |
     |                              | create Checkout Session    |
     |                              |--------------------------->|
     |<-----------------------------| checkoutUrl + paymentUrl   |
     | redirect payer               |                            |
     |----------------------------->| /pay/:id (hosted)          |
     |                              | redirect to Stripe         |
     |                              |                            |
     |                              |<----- checkout.session.completed
     |                              | POST /api/webhooks/stripe  |
     |                              | completeStripePayment()    |
     |<----- payment.forwarded -----| outbound webhook           |

PayDirect deployment (ops)

STRIPE_API_KEY=sk_live_...          # or STRIPE_SECRET_KEY
STRIPE_WEBHOOK_SECRET=whsec_...     # from Stripe Dashboard destination

Stripe Dashboard webhook destination

  • URL: https://www.paydirect.com/api/webhooks/stripe
  • Events from: Your account (not Connected accounts, unless you use Connect)
  • API version: 2026-05-27.dahlia
  • Payload style: Snapshot
  • Events: checkout.session.completed, checkout.session.expired, payment_intent.payment_failed

Use one destination per environment. Multiple destinations with different signing secrets will break verification.

Live vs sandbox

EnvironmentStripe behavior
pd_test_ sandboxAuto-completes lifecycle instantly. No real Checkout session.
pd_live_ liveReturns checkoutUrl. Fulfillment via Stripe webhook → forwarded.

See also: Stripe inbound webhooks, Merchant checkout guide.

Merchant Integration
Add crypto + card checkout to your product (e.g. job listings, subscriptions, agent marketplace)

Merchants integrate PayDirect as the payment layer. You choose the rail at checkout time; fulfillment uses the same outbound webhooks and POST /api/v1/verify for both rails.

1. Register outbound webhooks (your server)

POST /api/v1/webhooks
{
  "url": "https://your-app.com/webhooks/paydirect",
  "events": ["payment.forwarded", "payment.failed", "payment.expired"]
}

Fulfill orders on payment.forwarded. Check payment.paymentProvider (onchain or stripe) if you display different receipts.

2. Create payment when user checks out

// User chose "Pay with card"
const res = await fetch("https://www.paydirect.com/api/v1/payments", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${PAYDIRECT_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": `order-${orderId}-stripe`,
  },
  body: JSON.stringify({
    paymentMethod: "stripe",
    tokenSymbol: "USD",
    amount: "49.00",
    description: "Pro listing — 30 days",
    metadata: { orderId, userId, listingId },
  }),
});
const { checkoutUrl, paymentUrl, payment } = await res.json();

// Option A: redirect to Stripe Checkout directly (live)
if (checkoutUrl) window.location.href = checkoutUrl;

// Option B: hosted PayDirect page (works for both rails)
// window.location.href = paymentUrl;  // https://www.paydirect.com/pay/{id}

3. Crypto checkout (unchanged)

const res = await fetch("https://www.paydirect.com/api/v1/payments", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${PAYDIRECT_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": `order-${orderId}-crypto`,
  },
  body: JSON.stringify({
    paymentMethod: "crypto",
    tokenSymbol: "USDC",
    amount: "49.00",
    merchantWallet: MERCHANT_BASE_ADDRESS,
    metadata: { orderId, userId, listingId },
  }),
});
const { receivingAddress, paymentUrl, payment } = await res.json();
// Show QR / address, or send user to paymentUrl

4. Confirm before fulfilling (poll or webhook)

// After user returns from Checkout, or on webhook receipt:
const { verified, payment } = await fetch("https://www.paydirect.com/api/v1/verify", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${PAYDIRECT_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ paymentId: payment.id }),
}).then((r) => r.json());

if (verified) {
  await activateListing(payment.metadata.listingId);
}

Integration checklist

  • Use separate Idempotency-Key values per rail (e.g. order-123-stripe vs order-123-crypto).
  • Store payment.id on your order record before redirecting the payer.
  • Prefer payment.forwarded webhook over polling for production fulfillment.
  • Stripe minimum charge: $0.50 USD.
  • USD fee: 1.5% (Free tier), same model as USDC.
POST
/api/v1/payments
Create a new payment

Request Body

FieldTypeRequiredDescription
paymentMethodstringNo"crypto" (default) or "stripe"
tokenSymbolstringYes*Crypto: USDC, ETH, ADAO. Stripe: USD
amountstringYesPositive amount (e.g. "15.00")
merchantWalletstringCrypto: YesDestination Base address (0x...). Optional for Stripe (uses workspace settlement address).
returnUrlstringNoCustomer return URL. Surfaces as a Return to merchant button on the hosted checkout (success, cancel, expired screens). Also used as the Stripe Checkout success_url for the Stripe rail. Must be http(s)://. Aliases: successUrl, merchantReturnUrl, or the same keys nested in metadata.
cancelUrlstringNoStripe Checkout cancel redirect URL. Must be http(s)://.
descriptionstringNoHuman-readable description
metadataobjectNoArbitrary key-value metadata (e.g. orderId)
expiresInMinutesnumberNoMinutes until expiry (default: 60)
walletTypestringNo"eoa" or "smart_wallet". Which wallet receives the payment. Defaults to workspace setting.

Headers

HeaderRequiredDescription
AuthorizationYesBearer pd_test_... or pd_live_...
Idempotency-KeyNoUnique string to prevent duplicate payments

Stripe cURL Example

curl -X POST https://www.paydirect.com/api/v1/payments \
  -H "Authorization: Bearer pd_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "paymentMethod": "stripe",
    "tokenSymbol": "USD",
    "amount": "25.00",
    "description": "Pro subscription"
  }'

Crypto cURL Example

curl -X POST https://www.paydirect.com/api/v1/payments \
  -H "Authorization: Bearer pd_test_abc123..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-123-attempt-1" \
  -d '{
    "tokenSymbol": "USDC",
    "amount": "100.00",
    "merchantWallet": "0xYourBaseAddress...",
    "description": "Invoice #1234",
    "metadata": { "orderId": "ORD_123", "customer": "alice" }
  }'

TypeScript SDK

const { payment, receivingAddress, paymentUrl } = await client.createPayment({
  tokenSymbol: "USDC",
  amount: "100.00",
  merchantWallet: "0xYourBaseAddress...",
  description: "Invoice #1234",
  metadata: { orderId: "ORD_123" },
});

// Share paymentUrl with your customer — they can pay without an API key
// e.g. https://www.paydirect.com/pay/a1b2c3d4-e5f6-...

Response — Crypto (201 Created)

{
  "payment": { "id": "...", "paymentProvider": "onchain", "tokenSymbol": "USDC", "status": "pending", ... },
  "paymentMethod": "crypto",
  "paymentProvider": "onchain",
  "receivingAddress": "0xWorkspaceWalletAddress...",
  "paymentUrl": "https://www.paydirect.com/pay/a1b2c3d4-e5f6-..."
}

Response — Stripe live (201 Created)

{
  "payment": { "id": "...", "paymentProvider": "stripe", "tokenSymbol": "USD", "status": "pending", ... },
  "paymentMethod": "stripe",
  "paymentProvider": "stripe",
  "checkoutUrl": "https://checkout.stripe.com/c/pay/cs_live_...",
  "stripeCheckoutSessionId": "cs_live_...",
  "paymentUrl": "https://www.paydirect.com/pay/a1b2c3d4-e5f6-..."
}
GET
/api/v1/payments
List payments for the workspace

Query Parameters

ParameterTypeDescription
statusstringFilter by status (pending, detected, confirmed, forwarded, failed, expired, cancelled)
environmentstringFilter by environment (sandbox, live)
limitnumberMax results (1-100, default: 50)
offsetnumberPagination offset (default: 0)

Example

curl "https://www.paydirect.com/api/v1/payments?status=forwarded&limit=10" \
  -H "Authorization: Bearer pd_test_abc123..."

Response (200 OK)

{
  "payments": [ { ... }, { ... } ],
  "total": 42
}
GET
/api/v1/payments/:id
Retrieve a single payment by ID

Path Parameters

ParameterDescription
idPayment UUID
curl https://www.paydirect.com/api/v1/payments/a1b2c3d4-e5f6-... \
  -H "Authorization: Bearer pd_test_abc123..."

Response (200 OK)

{
  "payment": {
    "id": "a1b2c3d4-e5f6-...",
    "status": "forwarded",
    "tokenSymbol": "USDC",
    "grossAmount": "100.00",
    "feeAmount": "1.50",
    "netAmount": "98.50",
    "paymentProvider": "onchain",
    ...
  }
}

Returns 404 if the payment does not exist or does not belong to the authenticated workspace.

POST
/api/v1/payments/:id/cancel
Cancel a pending payment
curl -X POST https://www.paydirect.com/api/v1/payments/a1b2c3d4.../cancel \
  -H "Authorization: Bearer pd_test_abc123..."

Response (200 OK)

{
  "payment": {
    "id": "a1b2c3d4-...",
    "status": "cancelled",
    ...
  }
}

Only payments in pending status can be cancelled. Returns 400 if the payment is already confirmed or forwarded. Fires a payment.cancelled webhook event.

POST
/api/v1/verify
Verify that a payment has been settled

Crypto payments are verified when status is forwarded. Stripe payments verify at forwarded or confirmed.

Request Body

FieldTypeDescription
paymentIdstringThe payment UUID to verify
curl -X POST https://www.paydirect.com/api/v1/verify \
  -H "Authorization: Bearer pd_test_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"paymentId": "a1b2c3d4-..."}'

Response (200 OK)

{
  "verified": true,
  "payment": {
    "id": "a1b2c3d4-...",
    "status": "forwarded",
    ...
  }
}

verified is true only when the payment status is forwarded (fully settled). Use this endpoint to confirm settlement before fulfilling orders.