Inbound Card Capture
Capture cards server-to-server (no browser) by POSTing raw card data to the inbound capture proxy, which tokenizes the PAN and saves a payment method. For phone/MOTO orders, back-office tools, and migrations.
What this is and who needs it
Inbound Card Capture lets you save a card server-to-server, without a browser. Your backend POSTs the raw card to EPD’s inbound capture URL; the proxy tokenizes the PAN (the card number itself) in a PCI-certified environment and creates a payment method on the customer. The PAN never reaches EPD’s API; only an opaque token does.
Reach for this when there’s no browser to run EPD Elements in:
- Phone / MOTO orders (mail-order/telephone-order): an agent keys the card into your own system.
- Back-office tools: an operator saves a card on a customer’s behalf.
- Migrations: moving an existing book of cards onto EPD from your own PCI-compliant store.
For web and app checkout, use EPD Elements instead: it keeps the PAN out of your servers entirely.
This flow puts the raw card number on your backend. Unlike EPD Elements, where the card goes straight from the shopper’s browser into the vault and keeps your servers out of the cardholder-data path, here your server handles the PAN on its way to the proxy, which brings it into your PCI DSS scope. Handling raw card data carries materially heavier PCI obligations than a browser capture flow, and your exact validation requirements depend on your full payment channel and your acquirer. Confirm your obligations with your acquirer or a QSA before using this flow, and prefer EPD Elements whenever a browser is involved.
What EPD provisions for you
To capture cards inbound you authenticate with two things:
https://secure.epd.com: a single EPD endpoint, the same for every merchant. It tokenizes the PAN before it reaches the EPD API; EPD resolves your account from your secret key (below), not from the URL.
epd_live_sk_… / epd_test_sk_…. Sent as a normal Authorization: Bearer header and forwarded to EPD untouched: it’s how EPD resolves your account and saves the card under it.
The card is bound to your account by your secret key, server-side, and is guarded against cross-account replay: a token captured under one account can never be saved to another.
The flow
Your backend obtains the raw card details (e.g. an agent enters them for a phone order).
Send the card and the target customer_id to the inbound capture URL, authenticated with your secret key.
The capture proxy vaults the card in a PCI-certified environment and forwards only an opaque token to EPD. The PAN never reaches the EPD API.
EPD vaults the card under your account and returns a standard payment method.
Save a card
POST the card to the inbound capture URL with your secret key:
curl -X POST "https://secure.epd.com" \
-H "Authorization: Bearer epd_test_sk_…" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "550e8400-e29b-41d4-a716-446655440000",
"card": {
"number": "4111111111111111",
"exp_month": "12",
"exp_year": "2029",
"cvc": "999"
},
"billing_details": {
"name": "Jane Doe",
"address": {
"line1": "1 Main St",
"city": "Austin",
"state": "TX",
"postal_code": "78701",
"country": "US"
}
},
"set_as_default": false
}'
Request fields
| Field | Required | Description |
|---|---|---|
customer_id | yes | The customer the card is saved to (cus_… or a bare UUID). Must exist for your account. |
card.number | yes | The card number (PAN). Tokenized by the proxy; never stored by EPD. |
card.exp_month | yes | Expiry month, 1–12. |
card.exp_year | yes | Expiry year, 4-digit (2029). A 2-digit year (29) is also accepted and normalized to 4 digits. |
card.cvc | no | Card security code. Forwarded to the vault only when present. |
billing_details | no | Cardholder name + billing address. See below. |
set_as_default | no | Make this the customer’s default payment method. Defaults to false. |
update_subscriptions | no | When set_as_default is true, also switch active subscriptions to this card. Defaults to false. |
billing_details mirrors the object used elsewhere: name, email, phone, company, and an address (line1, line2, city, state, postal_code, country as ISO 3166-1 alpha-2). Every field is optional. Supplying a complete address also enables network-token provisioning: the card networks (Visa, Mastercard, …) issue a device-specific replacement number behind the scenes, so charges keep working automatically after the physical card is reissued or its number changes, with no action needed from your integration. This is unrelated to card_token (the one-time EPD Elements capture token); network tokens are a separate, card-network-level mechanism applied to the card you’ve already saved. An unrecognized country is skipped silently rather than failing the capture. If you omit the cardholder name, it falls back to the customer’s name so the saved card never shows as a bare “Credit Card”.
Some processors require a billing address before they will vault a card. If yours does, include billing_details.address with line1, city, state, postal_code, and country.
Response
On success you get back the same payment method object as Add Payment Method, 201 Created:
{
"id": "6ba7b815-9dad-11d1-80b4-00c04fd430c8",
"type": "card",
"card": { "brand": "visa", "last4": "1111", "card_expires": "12/2029" },
"customer": "550e8400-e29b-41d4-a716-446655440000",
"is_default": false,
"created_at": "2024-01-15T10:30:00.000Z"
}
From here the card behaves exactly like any vaulted payment method; use its id for orders and subscriptions.
Errors
All errors use the standard error envelope. Branch on code, not message.
| When | type | code | param | What to do |
|---|---|---|---|---|
card.number / exp_month / exp_year missing | invalid_request_error | N/A | N/A | Send all required card fields. |
| Expiry out of range | invalid_request_error | N/A | N/A | exp_month must be 1–12 and exp_year a valid year. |
| Card couldn’t be read or verified | invalid_request_error | invalid_request | runtime_token | Re-capture the card and submit it again. |
| Processor requires a billing address | invalid_request_error | invalid_request | billing_details | Resend with billing_details.address (line1, city, state, postal_code, country). |
| Customer doesn’t exist for your account | invalid_request_error | resource_not_found | N/A | Check the customer_id. |
Card data is never echoed back in an error; re-capture and retry rather than logging or inspecting the value you sent.
You send card.number / exp_month / exp_year / cvc, but a card-read error reports param: runtime_token. That’s the internal, tokenized form the proxy turns your PAN into before it reaches EPD’s API; EPD never sees the raw card fields, so it names the tokenized value. Treat it as “the card you captured” and re-capture.
Sandbox
Use a test secret key (epd_test_sk_…) against the inbound capture URL and a test card (e.g. 4111 1111 1111 1111 for a Visa success, or 4000 0000 0000 0002 to simulate a decline). The saved payment method works against the sandbox orders and subscriptions endpoints exactly as in production.
How it protects you
- The PAN is tokenized in a PCI-certified environment and never reaches EPD’s API; EPD only ever sees an opaque token.
- The captured card is bound to your account server-side from your secret key, and is guarded against cross-account replay.
- Capture is rate-limited per account to blunt card-testing.
Capturing cards in a browser? EPD Elements keeps the PAN out of your servers entirely (PCI SAQ A). Both produce the same payment method.