Partner API Reference
Quick-reference for all partner-accessible endpoints. For flow diagrams, webhook verification examples, and PCI scope notes see PARTNER_GUIDE.html.
Base URL and Authentication
| Environment | Base URL |
|---|---|
| Production | https://api.pay.emun1.com |
| Nonprod (testing) | https://nonprod.pay.emun1.com |
Authorization: ApiKey <your-api-key>
Content-Type: application/json
Every request is automatically scoped to your merchant account. API keys are generated in the portal under Settings → API Keys.
Payment Requests
Create a Payment Request
POST /v1/payment-requests
Request body
| Field | Type | Required | Description |
|---|---|---|---|
referenceId |
string | Yes | Your order/invoice number. Returned in all webhooks. |
buyerEmail |
string | Yes | Buyer email. Enables saved-card recall for returning buyers. |
amount |
number | Yes | Amount in dollars (e.g. 312.50). Range: 0.01 – 1,000,000. |
currency |
string | No | ISO 4217. Default: "USD". "CAD" is also supported. |
captureMode |
string | No | "manual" (default) or "immediate". |
acceptedPaymentMethod |
string | No | "card", "ach", or omit to let buyer choose. |
buyerCompany |
string | No | Company name shown in the portal. |
returnUrl |
string | No | Redirect after payment on mobile. |
billingAddress |
object | No | See Billing Address. |
lineItems |
array | No | See Line Items. Amounts in cents. |
Response 201
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"token": "abc123xyz...",
"iframeUrl": "https://checkout.pay.emun1.com/enter/abc123xyz...",
"status": "Pending",
"expiresAt": "2024-06-01T13:00:00Z"
}
Expires after 60 minutes.
Get Payment Request Status
GET /v1/payment-requests/{token}
Pass the string token (not the UUID) returned from the create call.
Response 200
{
"id": "3fa85f64-...",
"referenceId": "ORD-20240601-001",
"buyerEmail": "buyer@example.com",
"buyerCompany": "Acme Wholesale",
"amount": 312.50,
"currency": "USD",
"status": "Collected",
"createdAt": "2024-06-01T12:00:00Z",
"expiresAt": "2024-06-01T13:00:00Z",
"completedAt": "2024-06-01T12:34:56Z",
"acceptedPaymentMethod": "card"
}
| Status | Meaning |
|---|---|
Pending |
Waiting for buyer to complete payment. |
Collected |
Card entered. Ready to authorize. |
Expired |
60-minute window elapsed. |
Cancelled |
Cancelled by the platform. |
Request Card Refresh
When a stored card has expired or been declined during authorization, send the buyer a link to enter a replacement card.
POST /v1/payment-requests/{id}/card-update
The {id} is the UUID of the source payment request (status must be Collected).
Request body
| Field | Type | Required | Description |
|---|---|---|---|
returnUrl |
string | No | Mobile redirect after card entry. |
Response 201
{
"id": "9b1b3a2e-...",
"token": "xyz789...",
"iframeUrl": "https://checkout.pay.emun1.com/enter/xyz789...",
"expiresAt": "2024-06-01T14:00:00Z"
}
Show or send the iframeUrl to the buyer. On completion:
- The source payment request's stored card is replaced with the new one.
- A
card.updatedwebhook is delivered to your endpoint. - Re-call
POST /v1/paymentswith the samepaymentRequestId— the new card is used automatically.
Expires after 60 minutes.
HostedPCI Card Collection
A small number of merchants haven't migrated their actual payment processing to Adyen yet and still
use a third-party vendor, HostedPCI, for PCI-compliant card capture. For these merchants, Foundry
shows the HostedPCI iframe instead of the Adyen Drop-in inside the same iframeUrl you already embed
— the POST /v1/payment-requests and GET /v1/payment-requests/{token} contracts above are
unchanged, and the payment request still reaches Collected the same way.
The difference only shows up after collection: HostedPCI-mode payment requests never reach
Authorized/Captured/etc. through Foundry — POST /v1/payments and the batch authorize/capture
endpoints do not apply to them. In most cases you already process the charge yourself on your own
downstream gateway (Authorize.Net, Stripe, etc.), using the vendor's own gateway credentials. To do
that, retrieve a Gateway Token for the collected card:
Retrieve Gateway Token
POST /v1/payment-requests/{id}/gateway-token
The {id} is the UUID of a Collected payment request for a HostedPCI-mode merchant.
Response 200
{
"gatewayToken": "4111...tok",
"gatewayResponseStatus": "Approved",
"gatewayResponseCode": "00"
}
| Field | Description |
|---|---|
gatewayToken |
The vendor-specific token to send to your own downstream gateway. Can be empty when the vendor has no separate gateway configured (HostedPCI itself is the terminal processor) — this is expected, not an error. |
gatewayResponseStatus / gatewayResponseCode |
The downstream gateway's own status/code for this tokenization, if any. |
Call this whenever you need the token — right after the payment_request.completed webhook fires, or
later. It's independent of card collection itself. A 409 means the payment request isn't a
Collected HostedPCI-mode request; a 502 means the vendor's gateway tokenization itself failed
(detail carries the underlying error).
Card Enrollment
Store a card on file without tying it to any specific order or invoice.
POST /v1/card-enrollments
Request body
| Field | Type | Required | Description |
|---|---|---|---|
buyerEmail |
string | Yes | Buyer email. Used as the shopper identifier for returning buyers. |
buyerReference |
string | No | Your own buyer/account ID. Returned in the card.enrolled webhook. |
buyerCompany |
string | No | Company name (display only). |
returnUrl |
string | No | Mobile redirect after card entry. |
billingAddress |
object | No | Pre-populated in the form. See Billing Address. |
Response 201
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"token": "abc123xyz...",
"iframeUrl": "https://checkout.pay.emun1.com/enter/abc123xyz...",
"expiresAt": "2024-06-01T13:00:00Z"
}
Show the iframeUrl to the buyer. On completion you receive a card.enrolled webhook with the
paymentMethodId. Use that ID in future POST /v1/payments calls.
Expires after 60 minutes.
Payments
Authorize
Place an authorization hold on the funds for a Collected payment request. This is the
API equivalent of clicking Authorize on the payment request's detail page in the portal —
useful if you'd rather trigger it from your own systems than have someone do it manually. Only
applies to "manual" capture-mode payment requests — "immediate" ones are already authorized
(and captured) automatically as soon as the buyer submits the payment form, and calling this
endpoint on one returns an error.
POST /v1/payments
Request body
| Field | Type | Required | Description |
|---|---|---|---|
paymentRequestId |
UUID | Yes | ID of the Collected payment request. |
Response 200
{
"id": "7c4a9e1d-...",
"status": "Authorized",
"pspReference": "853612...",
"amount": 312.50,
"currency": "USD",
"referenceId": "ORD-20240601-001",
"authorizedAt": "2024-06-01T12:35:00Z"
}
| Payment Status | Meaning |
|---|---|
Authorized |
Funds held. Capture via portal to settle. |
PartiallyCaptured |
One or more partial captures settled; remaining authorized balance is still open. |
Captured |
Full authorized amount settled (immediate-capture mode or all captures complete). |
Failed |
Authorization declined. |
Voided |
Authorization cancelled before (or after partial) capture. |
Refunded |
Post-capture refund issued. |
Capture
Capture (settle) an authorized payment — the API equivalent of clicking Capture on the
payment's detail page in the portal. Only Authorized or PartiallyCaptured payments can be
captured.
POST /v1/payments/{id}/capture
Request body (optional — omit entirely to capture the full remaining balance)
| Field | Type | Required | Description |
|---|---|---|---|
amount |
number | No | Amount to capture. Defaults to the full remaining authorized balance. Use for split-shipment workflows — capture again later with the remaining amount once the rest ships. |
Response 200
{
"id": "7c4a9e1d-...",
"status": "Captured",
"authorizedAmount": 312.50,
"capturedAmount": 312.50,
"remainingAmount": 0,
"currency": "USD",
"referenceId": "ORD-20240601-001",
"capturedAt": "2024-06-01T13:00:00Z"
}
A capture smaller than the remaining authorized amount moves status to PartiallyCaptured
rather than Captured — submit another Capture call against the same id later for the rest.
If you're capturing many payments at once, Batch Capture below does the same
thing across a whole list in one call, including partial/split-shipment amounts.
Void
Cancel the outstanding, uncaptured portion of an authorization — the API equivalent of clicking
Void on the payment's detail page in the portal. Only Authorized or PartiallyCaptured
payments can be voided; any amount already captured before voiding stays captured and settled —
voiding only cancels what's left of the hold.
POST /v1/payments/{id}/void
No request body.
Response 200
{
"id": "7c4a9e1d-...",
"status": "Voided",
"authorizedAmount": 312.50,
"capturedAmount": 0,
"currency": "USD",
"referenceId": "ORD-20240601-001"
}
Refund
Refund a captured payment, in full or in part. Refunds settle asynchronously — the response reflects the refund request being accepted, not final confirmation from the card network.
POST /v1/payments/{id}/refund
Request body
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | Yes | One of RequestedByCustomer, IssueWithItemSold, Fraudulent, Duplicate, Other. |
amount |
number | No | Amount to refund. Defaults to the full refundable balance (captured amount minus any prior refunds). |
Response 200
{
"id": "7c4a9e1d-...",
"status": "Refunded",
"authorizedAmount": 312.50,
"capturedAmount": 312.50,
"referenceId": "ORD-20240601-001"
}
status moves to Refunded only once the refund covers the full captured amount — a partial
refund leaves status as Captured, with the partial refund tracked internally against that
payment. Only Captured (or already-partially-refunded) payments can be refunded.
Batch Authorize
Authorize multiple payment requests in one call. Always returns HTTP 200; inspect each item's
success field — the batch status code does not indicate per-item success.
POST /v1/payments/batch-authorize
Request body
{
"items": [
{ "referenceId": "ORD-001" },
{ "referenceId": "ORD-002", "amount": 150.00 }
]
}
| Field | Type | Required | Description |
|---|---|---|---|
items[].referenceId |
string | Yes | Your order/invoice reference. |
items[].amount |
number | No | Override the payment request amount (advanced use). |
Response 200
{
"results": [
{ "referenceId": "ORD-001", "success": true, "paymentId": "7c4a9e1d-..." },
{ "referenceId": "ORD-002", "success": false, "error": "No collected payment request found" }
],
"summary": { "total": 2, "succeeded": 1, "failed": 1 }
}
Batch Capture
Capture multiple authorized (or partially captured) payments in one call. Supports partial amounts and per-capture shipment references for split-shipment workflows.
POST /v1/payments/batch-capture
Request body
{
"items": [
{ "referenceId": "ORD-001" },
{ "referenceId": "ORD-002", "amount": 150.00, "shipmentReference": "SHIP-A" }
]
}
| Field | Type | Required | Description |
|---|---|---|---|
items[].referenceId |
string | Yes | Your order/invoice reference. |
items[].amount |
number | No | Amount to capture; defaults to the full remaining balance. Use for split-shipment workflows. |
items[].shipmentReference |
string | No | Optional tag for this capture (e.g. "SHIP-A"). Returned in capture history for reconciliation. |
Response 200 — same per-item { referenceId, success, paymentId, error } format as Batch
Authorize. Always HTTP 200; inspect each item's success field.
- A partial capture moves the payment to
PartiallyCaptured; submit the same reference again in a future batch to capture the next shipment. - If
amountexceeds the remaining uncaptured balance the item fails with a clear error.
Authorization Extension & Reauthorization
Manual-capture authorizations left uncaptured for too long can expire on the card network before you capture them. Foundry proactively tries to keep an aging authorization alive, and falls back to transparently re-authorizing the remaining balance (same stored payment method, new authorization) if that isn't possible.
This means pspReference on a payment is not permanently fixed — it reflects the current live
authorization and can change if a reauthorization happens. Use id (the Foundry payment ID) or your own
referenceId as your stable identifier for reconciliation, not pspReference.
| Payment Status | Meaning |
|---|---|
Expired |
The authorization expired before capture and could not be automatically reauthorized. Nothing was captured. A partially-captured payment whose remaining balance suffers this stays PartiallyCaptured — only the un-captured remainder is affected. |
GET /v1/transactions includes an authorizationEvents array per payment recording any extension or
reauthorization attempts (outcome, and — for a successful reauthorization — the old and new
pspReference), useful for reconciliation if you track PSP references on your side.
Transactions
List Transactions
GET /v1/transactions
Query parameters
| Parameter | Type | Description |
|---|---|---|
status |
string | Filter by payment status (e.g. Authorized, Captured, Expired). |
search |
string | Full-text search on reference ID or buyer email. |
paymentRequestId |
UUID | Return only transactions for a specific payment request. |
from |
ISO 8601 | Earliest transaction date. |
to |
ISO 8601 | Latest transaction date. |
page |
integer | Page number (1-based). |
pageSize |
integer | Results per page (default 20). |
Response 200 — paginated list of payment objects. Each includes an authorizationEvents array —
see "Authorization Extension & Reauthorization" above.
Payouts
Deposit reconciliation — what was paid into your bank account, and which sales, refunds, and chargebacks made up each deposit. A different question from Transactions above (which is per-transaction economics) — see the portal's own Payouts page for the equivalent UI.
List Payouts
GET /v1/payouts
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
from |
ISO 8601 date | Yes | Earliest deposit date. |
to |
ISO 8601 date | Yes | Latest deposit date. |
search |
string | No | Matches order/invoice reference or PSP reference of a transaction within the deposit. |
minAmount |
number | No | Minimum deposit (net) amount. |
maxAmount |
number | No | Maximum deposit (net) amount. |
page |
integer | No | Page number (1-based). |
pageSize |
integer | No | Results per page (default 25). |
Response 200
{
"total": 5,
"page": 1,
"pageSize": 25,
"items": [
{
"payoutDate": "2024-06-16T07:00:25Z",
"grossAmount": 13318.96,
"feeAmount": 145.00,
"netAmount": 13173.96,
"currency": "USD",
"transactionCount": 6
}
]
}
payoutDate is the deposit's unique identifier for the two endpoints below — pass it back exactly
as returned.
Get Payout Detail
Every transaction, refund, and chargeback that composed one specific deposit.
GET /v1/payouts/detail
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
payoutDate |
ISO 8601 | Yes | The exact payoutDate value from a List Payouts item. |
Response 200
{
"items": [
{
"id": "8f2a1c3e-...",
"lineType": "Capture",
"description": "Seller split",
"amount": 9412.80,
"currency": "USD",
"pspReference": "TSFTZXXT98KPT9V5",
"referenceId": "ORD-1005",
"customerName": "Acme Wholesale"
}
]
}
lineType |
Meaning |
|---|---|
Capture |
A sale's net proceeds credited toward this deposit. |
Refund |
A refund debited from this deposit. |
Chargeback |
A chargeback debited from this deposit. |
referenceId/customerName are null when the line couldn't be matched back to one of your own
payments (rare — see DEVGUIDE if you need the details).
Get Payout Summary
Aggregate totals for a date range — the same figures shown on the portal's Payouts page.
GET /v1/payouts/summary
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
from |
ISO 8601 date | Yes | Start of range. |
to |
ISO 8601 date | Yes | End of range. |
Response 200
{
"depositsAmount": 13173.96,
"depositsCount": 1,
"transactionCount": 6,
"chargebacksAmount": 0,
"refundsAmount": 145.00,
"totalDeductions": 145.00,
"netPayout": 13173.96
}
A chargeback or refund can be booked in a different deposit period than its original sale —
totalDeductions and the change in depositsAmount may not tie out exactly for a given range for
that reason; this is normal payout-reconciliation timing, not a data error.
Export Payouts (CSV)
GET /v1/payouts/export
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
from |
ISO 8601 date | Yes | Start of range. |
to |
ISO 8601 date | Yes | End of range. |
mode |
string | No | "payouts" (default) — one row per deposit. "details" — one row per transaction/refund/chargeback, with its parent deposit's date/amount repeated on each row. |
Response 200 — text/csv, filename payouts-{mode}.csv.
Shared Object Schemas
Billing Address
{
"street": "123 Main St",
"houseNumberOrName": "Suite 400",
"city": "Chicago",
"stateOrProvince": "IL",
"postalCode": "60601",
"country": "US"
}
All fields optional; any provided are forwarded to the card network for AVS.
Line Items
Array of objects for Level 3 interchange qualification. Amounts in cents.
[
{
"id": "SKU-001",
"description": "Widget A",
"quantity": 2,
"amountExcludingTax": 25000,
"amountIncludingTax": 25000,
"taxAmount": 0,
"taxPercentage": 0,
"commodity": "widgets",
"upc": "012345678905",
"brand": "Acme",
"imageUrl": "https://yourapp.example.com/images/widget-a.png",
"productUrl": "https://yourapp.example.com/products/widget-a"
}
]
Any other fields you include (e.g. your own internal productCode) are stored but stripped before
being sent to Adyen — only the fields above are forwarded for L2/L3 enrichment.
Webhooks
Envelope
All webhooks share a common envelope:
{
"eventType": "payment_request.completed",
"createdAt": "2024-06-01T12:34:56Z",
"data": { }
}
Event Payloads
payment_request.completed
{
"referenceId": "ORD-20240601-001",
"status": "collected",
"paymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
card.enrolled
{
"buyerReference": "BUYER-4521",
"buyerEmail": "buyer@example.com",
"paymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"last4": "1111",
"brand": "visa"
}
card.updated
{
"referenceId": "ORD-20240601-001",
"paymentMethodId": "7c4a9e1d-...",
"last4": "4242",
"brand": "mastercard"
}
payment_request.expired
{
"referenceId": "ORD-20240601-001"
}
Signature Verification
Every webhook includes X-Foundry-Signature: sha256=<hex-digest>.
import hmac, hashlib
def verify(secret: str, body: bytes, header: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, header)
bool Verify(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));
}
Respond with any 2xx to acknowledge. Failed deliveries are retried 3 times with exponential
backoff.
Error Responses
{ "error": "Payment request not found or not in collected state" }
| HTTP Status | Meaning |
|---|---|
400 |
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 |
Payment could not be processed (e.g. declined). |
502 |
Upstream error from payment network. Retry after a short delay. |
Test Cards
| Scenario | Card Number | Expiry | CVV |
|---|---|---|---|
| Visa — success | 4111 1111 1111 1111 |
Any future | Any 3 digits |
| Mastercard — success | 5500 0000 0000 0004 |
Any future | Any 3 digits |
| Declined | 4000 0000 0000 0002 |
Any future | Any 3 digits |
| Insufficient funds | 4000 0000 0000 9995 |
Any future | Any 3 digits |
ACH testing: routing 021000021, any 10-digit account number.