Partner Integration Guide

Getting started with the Foundry Pay hosted payment service

Foundry Pay is a hosted payment service. Your platform creates a payment request, the buyer completes payment inside a Foundry-hosted iframe (card entry, stored cards, or ACH), and Foundry notifies you via webhook when the payment is captured. Card data never passes through your servers.


Quick Start

  1. Receive your API key from Foundry Pay (one key per vendor/merchant).
  2. POST /v1/payment-requests → receive iframeUrl.
  3. Show the iframe to the buyer.
  4. Receive the payment_request.completed webhook when payment is captured.

Base URL

https://api.foundrypay.io

All requests require:

Authorization: ApiKey <your-api-key>
Content-Type: application/json

Authentication

API keys are per-merchant. Every request made with your key is automatically scoped to your merchant account — you do not need to pass a merchant ID.

API keys are generated in the Foundry Pay merchant portal under Settings → API Keys.


Creating a Payment Request

POST /v1/payment-requests

Request body

{
  "referenceId": "ORD-20240601-001",
  "buyerEmail": "buyer@example.com",
  "amount": 312.50,
  "currency": "USD",
  "captureMode": "manual",
  "acceptedPaymentMethod": "card",
  "returnUrl": "https://yourapp.com/checkout/return",
  "billingAddress": {
    "street": "123 Main St",
    "city": "Chicago",
    "stateOrProvince": "IL",
    "postalCode": "60601",
    "country": "US"
  },
  "lineItems": [
    {
      "id": "SKU-001",
      "description": "Widget A",
      "quantity": 2,
      "amountExcludingTax": 25000,
      "amountIncludingTax": 25000,
      "taxAmount": 0,
      "taxPercentage": 0
    }
  ]
}

Fields

Field Required Description
referenceId Yes Your order/invoice number. Returned in all webhooks.
amount Yes Payment amount in dollars (e.g. 312.50).
buyerEmail No Buyer's email address. Used as a shopper identifier so previously saved cards appear in the drop-in for returning buyers.
currency No ISO 4217 code. Defaults to "USD".
captureMode No "manual" (default) or "immediate". See Capture Modes.
acceptedPaymentMethod No "card", "ach", or omit to allow the buyer to choose. Omitting is only recommended with captureMode: "immediate".
returnUrl No Where the iframe redirects after payment on mobile. Not required for embedded desktop flows.
billingAddress No Forwarded to the card network for AVS and Level 3 data.
lineItems No Order line items for Level 3 interchange qualification. Amounts in cents (e.g. 25000 = $250.00).

Response

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "token": "abc123xyz...",
  "iframeUrl": "https://pay.foundrypay.io/enter/abc123xyz...",
  "status": "Pending",
  "expiresAt": "2024-06-01T13:00:00Z"
}

Payment requests expire after 60 minutes. Create a new one if the buyer does not complete payment in time.


Showing the Payment UI

Desktop — embedded iframe

<iframe
  src="https://pay.foundrypay.io/enter/{token}"
  width="100%"
  height="600"
  frameborder="0"
  allow="payment"
></iframe>

Poll GET /v1/payment-requests/{token} every few seconds and close/hide the iframe when status changes to "Collected".

Mobile — full-page redirect

window.location.href = iframeUrl;

Set returnUrl in the payment request so the buyer is redirected back to your app after payment completes. Add a query parameter to your return URL (e.g. ?foundryComplete=1) so your page knows to poll for the final status on load.


Checking Payment Status

GET /v1/payment-requests/{token}

Returns the current status of the payment request.

{
  "id": "3fa85f64-...",
  "referenceId": "ORD-20240601-001",
  "status": "Collected",
  "completedAt": "2024-06-01T12:34:56Z"
}
Status Meaning
Pending Waiting for buyer to complete payment.
Collected Payment captured. Safe to fulfill.
Expired 60-minute window passed without payment.
Cancelled Cancelled by the platform.

Recommendation: Use webhooks as the primary completion signal and polling only as a fallback for local development or environments without a public webhook URL.


Webhooks

Foundry Pay sends a POST to your configured webhook URL when payment events occur.

Configure your webhook URL

Set it in the merchant portal under Settings → Webhook.

Envelope

Every webhook is wrapped in a common envelope:

{
  "eventType": "payment_request.completed",
  "createdAt": "2024-06-01T12:34:56Z",
  "data": {  }
}

Events

payment_request.completed

Fired when a buyer completes card entry in the iframe (manual or immediate capture mode).

{
  "eventType": "payment_request.completed",
  "createdAt": "…",
  "data": {
    "referenceId": "ORD-20240601-001",
    "status": "collected",
    "paymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }
}

paymentMethodId is the Foundry-internal identifier for the stored card. You can pass it to POST /v1/payments for future charges. It is not a card number or Adyen token.

card.enrolled

Fired when a buyer completes a card enrollment (from POST /v1/card-enrollments).

{
  "eventType": "card.enrolled",
  "createdAt": "…",
  "data": {
    "buyerReference": "BUYER-4521",
    "buyerEmail": "buyer@example.com",
    "paymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "last4": "1111",
    "brand": "visa"
  }
}

card.updated

Fired when a buyer completes a card refresh (from POST /v1/payment-requests/{id}/card-update). The referenced payment request now has the new card stored and is ready to re-authorize.

{
  "eventType": "card.updated",
  "createdAt": "…",
  "data": {
    "referenceId": "ORD-20240601-001",
    "paymentMethodId": "7c4a9e1d-...",
    "last4": "4242",
    "brand": "mastercard"
  }
}

payment_request.expired

Fired when a payment request reaches its 60-minute expiry without being completed.

{
  "eventType": "payment_request.expired",
  "createdAt": "…",
  "data": {
    "referenceId": "ORD-20240601-001"
  }
}

Verifying webhook signatures

Every webhook includes an X-Foundry-Signature header:

X-Foundry-Signature: sha256=<hex-digest>

Verify it with your webhook secret (shown once when you configure the URL — store it securely):

import hmac, hashlib

def verify_signature(secret: str, body: bytes, header: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, header)
bool VerifySignature(string secret, byte[] body, string header)
{
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var digest = "sha256=" + Convert.ToHexString(hmac.ComputeHash(body)).ToLower();
    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(digest),
        Encoding.UTF8.GetBytes(header));
}

Always use a constant-time comparison to prevent timing attacks. Reject requests where the signature does not match.

Webhook delivery

Foundry Pay retries failed webhook deliveries 3 times with exponential backoff. Respond with any 2xx status to acknowledge receipt. If your endpoint is temporarily unavailable, delivery will be retried over several minutes.


Capture Modes

"manual" (default — order checkout)

The iframe tokenizes the card with a zero-dollar authorization. No money moves until your merchant account explicitly authorizes and captures the payment in the Foundry Pay portal.

Use this for order workflows where you want to review and fulfill before charging.

{
  "referenceId": "ORD-001",
  "buyerEmail": "buyer@example.com",
  "amount": 312.50,
  "captureMode": "manual",
  "acceptedPaymentMethod": "card"
}

"immediate" (invoice payments)

The iframe performs authorization and capture in a single step. Money moves when the buyer submits — no portal action required. Use this for invoice payments or any flow where you want funds captured at the point of collection.

{
  "referenceId": "INV-2024-005",
  "buyerEmail": "buyer@example.com",
  "amount": 875.00,
  "captureMode": "immediate"
}

When captureMode is "immediate" and acceptedPaymentMethod is omitted, the drop-in presents both card and ACH options. The buyer chooses at payment time — both are settled immediately.

ACH direct debit

ACH payments are always immediate — the bank debit occurs within the payment session. You can request ACH explicitly or let the buyer choose when using immediate capture:

{
  "referenceId": "ORD-001",
  "buyerEmail": "buyer@example.com",
  "amount": 312.50,
  "acceptedPaymentMethod": "ach"
}

Note: ACH settlement is asynchronous at the bank level (resultCode: "Received"). The payment_request.completed webhook fires immediately when the debit is initiated. Actual bank settlement typically occurs within 1–3 business days. Chargebacks can occur up to 60 days after the debit.


Stored Payment Methods

When buyerEmail is provided, the buyer's previously saved cards appear in the drop-in for one-click payment on return visits. The same email address must be passed consistently across payment requests for the same buyer.

Stored payment methods are managed entirely within Foundry Pay — your platform never sees card numbers, CVVs, or Adyen tokens.


Card Enrollment (Store a Card Without an Order)

Use card enrollment when you want to save a buyer's card on file before any specific order or invoice exists — for example, when onboarding a new account or when a sales rep wants to set up payment for a buyer in advance.

POST /v1/card-enrollments

Request body

{
  "buyerEmail": "buyer@example.com",
  "buyerReference": "BUYER-4521",
  "buyerCompany": "Acme Wholesale",
  "billingAddress": {
    "street": "123 Main St",
    "city": "Chicago",
    "stateOrProvince": "IL",
    "postalCode": "60601",
    "country": "US"
  }
}
Field Required Description
buyerEmail Yes Buyer's email address. Used as the shopper identifier in Adyen.
buyerReference No Your own buyer or account ID. Returned in the card.enrolled webhook for correlation.
buyerCompany No Company name (for display purposes).
billingAddress No Pre-populated in the card entry form; buyer can edit.
returnUrl No Where the iframe redirects after completion (mobile flow).

Response

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "token": "abc123xyz...",
  "iframeUrl": "https://pay.foundrypay.io/enter/abc123xyz...",
  "expiresAt": "2024-06-01T13:00:00Z"
}

Show the iframeUrl to the buyer in the same way as a payment request. When the buyer submits their card, you receive a card.enrolled webhook containing the paymentMethodId. Use that ID in future POST /v1/payments calls to charge the card.

Enrollment sessions expire after 60 minutes.


Card Refresh (Update a Card After Authorization Failure)

When an authorization attempt fails because the stored card has expired or been declined, you can send the buyer a link to enter a replacement card. Once submitted, the new card is automatically associated with the original payment request so you can immediately retry authorization.

POST /v1/payment-requests/{id}/card-update
Field Required Description
returnUrl No Where the iframe redirects after completion (mobile flow).

Response

{
  "id": "9b1b3a2e-...",
  "token": "xyz789...",
  "iframeUrl": "https://pay.foundrypay.io/enter/xyz789...",
  "expiresAt": "2024-06-01T14:00:00Z"
}

Send the iframeUrl to the buyer via email or SMS. When the buyer submits the new card: 1. The payment request's stored card is replaced with the new one. 2. You receive a card.updated webhook. 3. Re-call POST /v1/payments (or batch-authorize) with the same reference — the new card is used automatically.

Card update sessions expire after 60 minutes. Only Collected payment requests are eligible.


Refunds

Refunds are initiated through the Foundry Pay merchant portal (Transactions → select payment → Refund). Refund API access for automated workflows is available on request.


Testing

Use these Adyen test cards in the iframe when pointed at the test environment:

Scenario Card Number Expiry CVV
Visa — success 4111 1111 1111 1111 Any future date Any 3 digits
Mastercard — success 5500 0000 0000 0004 Any future date Any 3 digits
Declined 4000 0000 0000 0002 Any future date Any 3 digits
Insufficient funds 4000 0000 0000 9995 Any future date Any 3 digits

For ACH testing, use routing number 021000021 and any 10-digit account number.

Full Adyen test card list: https://docs.adyen.com/development-resources/testing/test-card-numbers/


PCI Scope

Foundry Pay is designed to keep your platform at SAQ-A (the lowest PCI DSS compliance tier):


Errors

All errors return a JSON body with an error field:

{ "error": "Payment request not found or not in collected state" }
HTTP Status Meaning
400 Bad request — missing required field or invalid value.
401 Invalid or missing API key.
404 Resource not found.
409 Conflict — e.g. payment request already collected or expired.
422 Unprocessable — payment could not be processed (e.g. declined).
502 Upstream error from the payment network. Retry after a short delay.

Support

Contact your Foundry Pay account manager or email support@foundrypay.io with your referenceId for any payment-specific inquiries.