> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spritz.finance/llms.txt
> Use this file to discover all available pages before exploring further.

# Push to debit card

> Pay an off-ramp out to a user's Visa or Mastercard debit card in minutes, without card numbers ever touching your servers.

Push to card is an [off-ramp](/guides/use-cases/off-ramp) whose destination is the user's
own debit card instead of a bank account. Funds land on the card in minutes, around the
clock. Everything about the quote, the on-chain payment, and the fiat leg is the same as
the bank-account flow — this guide covers only what differs: collecting the card safely
and choosing the `push_to_card` rail.

Push to card is available to verified US users, in USD, for Visa and Mastercard debit
cards. Credit cards are not eligible, and the card network has the final say on whether a
given card can receive a push.

## How card data stays out of scope

Spritz never accepts a raw card number, and neither should you. Card entry runs inside an
[Evervault](https://evervault.com) Card component: an iframe served from Evervault's
domain that encrypts the card number in the user's browser before your code ever sees it.
What comes out is an opaque `ev:` token plus the plaintext metadata Spritz needs (expiry,
last four, BIN, brand). You forward those to `POST /v1/debit-cards`; only Spritz's
infrastructure can decrypt the token, and only to hand the card to the payout network.

The component needs two identifiers to initialise: an Evervault **team ID** and an
**app ID**. They are public-key material, not credentials — anyone holding them can only
encrypt *to* Spritz, never decrypt — but they are environment-specific, so a card
encrypted for sandbox cannot be used in production and vice versa.

<Note>
  Spritz provides the Evervault team ID and the app IDs for sandbox and production
  during onboarding. If you do not have them, ask your Spritz contact.
</Note>

## Before you start

* Authenticate as an integrator acting for a user. See
  [Authentication](/guides/authentication).
* The user's capabilities on `GET /v1/users/me` include the pair `product:
  "crypto_to_fiat"` and `method: "push_to_card"` with `status: "active"`. See
  [Onboarding](/guides/onboarding#3-read-capabilities). Adding a card without it returns
  `403`.
* You have the Evervault team ID and the app ID for the environment you are calling.

## Step 1 — Collect the card in the browser

Install the Evervault SDK for your stack (`@evervault/react`, `@evervault/js`, or the
[mobile SDKs](https://docs.evervault.com/sdks)) and render the Card component with
`number` and `expiry` fields. Spritz does not need the CVC — leave it out.

```tsx theme={null}
import { EvervaultProvider, Card, type CardPayload } from "@evervault/react";

function AddDebitCard({ onCaptured }: { onCaptured: (payload: CardPayload) => void }) {
  return (
    <EvervaultProvider teamId={EVERVAULT_TEAM_ID} appId={EVERVAULT_APP_ID}>
      <Card
        fields={["number", "expiry"]}
        acceptedBrands={["visa", "mastercard"]}
        onChange={(payload) => {
          if (payload.isValid && payload.isComplete) onCaptured(payload);
        }}
      />
    </EvervaultProvider>
  );
}
```

The `onChange` payload carries everything the API needs:

| Payload field       | Sent as               | Notes                                                  |
| ------------------- | --------------------- | ------------------------------------------------------ |
| `card.number`       | `encryptedCardNumber` | Encrypted `ev:` token. Never log or store it yourself. |
| `card.expiry.month` | `expiryMonth`         | Plaintext, may be unpadded (`"9"`).                    |
| `card.expiry.year`  | `expiryYear`          | Plaintext, two digits (`"29"`).                        |
| `card.lastFour`     | `cardLastFour`        | Plaintext.                                             |
| `card.bin`          | `cardBin`             | Plaintext, six to eight digits.                        |
| `card.brand`        | `cardBrand`           | `visa` or `mastercard`.                                |

Collect the cardholder's first and last name and their US billing address in your own
form. The name must be the name printed on the card — Spritz checks it against the user's
verified identity — and the card network requires the billing address to accept pushes.

## Step 2 — Add the card

Send the captured values from your backend, signed with your integrator key, on behalf
of the user. Your backend only ever handles the encrypted token, which keeps it outside
PCI scope.

```bash theme={null}
curl -X POST https://platform.spritz.finance/v1/debit-cards \
  -H "Content-Type: application/json" \
  # plus your integrator signing headers and the user's Authorization (see Authentication)
  -d '{
    "encryptedCardNumber": "ev:SWFSS:…",
    "expiryMonth": "9",
    "expiryYear": "29",
    "cardLastFour": "4242",
    "cardBin": "424242",
    "cardBrand": "visa",
    "cardholderFirstName": "Jane",
    "cardholderLastName": "Doe",
    "billingAddress": {
      "line1": "123 Main St",
      "city": "Austin",
      "state": "TX",
      "postalCode": "78701",
      "country": "US"
    },
    "label": "Chase debit"
  }'
```

A `201` returns the card, ready to use:

```json theme={null}
{
  "id": "6a53f91ad75586ce264cd790",
  "status": "active",
  "network": "visa",
  "cardNumberLast4": "4242",
  "expiryMonth": 9,
  "expiryYear": 2029,
  "label": "Chase debit",
  "currency": "USD",
  "isTokenized": true,
  "createdAt": "2026-09-08T10:12:44.118Z"
}
```

Card statuses:

| Status            | Meaning                                                                                     | What to do                                                                                                                                                                            |
| ----------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `active`          | Ready for payouts                                                                           | Offer it as a destination                                                                                                                                                             |
| `rejected`        | The card network refused it — expired, inactive, or a card type that cannot receive payouts | Terminal. Ask the user to add a different card                                                                                                                                        |
| `inactive`        | Removed                                                                                     | Hide it                                                                                                                                                                               |
| `action_required` | Stored, but missing cardholder details                                                      | Only seen on cards added before name and billing address were required. Collect them and call `PATCH /v1/debit-cards/{id}/cardholder-info`; the `requirements` array lists the fields |

List a user's cards with `GET /v1/debit-cards` (`{ "data": [...], "hasMore": false }` —
no pagination, users hold few cards), read one with `GET /v1/debit-cards/{id}`, and
remove one with `DELETE /v1/debit-cards/{id}`. A deleted card reads back as `404`.

### Errors when adding a card

| Situation                                                                                 | Status | `type`                                                | Handle it                                                                                                                                                                                                                        |
| ----------------------------------------------------------------------------------------- | ------ | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Push to card is not active for the user                                                   | `403`  | `urn:problem-type:forbidden`                          | Re-read capabilities; the user may need to finish verification, or is outside the US                                                                                                                                             |
| Cardholder name is not a plausible personal name and does not match the verified identity | `422`  | `urn:problem-type:business:verification-failed`       | Ask the user to enter the name exactly as printed on the card. Do not retry the same name                                                                                                                                        |
| A field fails validation (bad BIN, unsupported brand, missing billing address)            | `400`  | `urn:problem-type:validation:invalid-fields`          | Fix the request; the `errors` array names each field                                                                                                                                                                             |
| The card could not be registered for payouts                                              | `503`  | `detail: "Failed to create debit card with provider"` | Nothing was stored. This is usually the card, not an outage — expired, a credit card, or an issuer that does not accept pushes. Show "This card can't receive payouts" and let the user try another card. Do not retry in a loop |

## Step 3 — Quote, pay, and track

From here the [off-ramp guide](/guides/use-cases/off-ramp#step-2--create-the-quote)
applies unchanged. Pass the card's `id` as `accountId` and choose the `push_to_card` rail:

```json theme={null}
{
  "accountId": "6a53f91ad75586ce264cd790",
  "amount": "100.00",
  "amountMode": "output",
  "rail": "push_to_card",
  "chain": "base",
  "tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
}
```

* **Timing**: minutes, seven days a week. The off-ramp usually moves `in_flight` →
  `completed` within a few minutes of the on-chain confirmation. See
  [Settlement timing](/guides/timing).
* **Pricing**: the crypto-to-fiat base rate plus the `push_to_card` adjustment, with a
  weekend and US-bank-holiday surcharge. See [End-user pricing](/guides/pricing).
* **Failure**: if the network declines the push after the crypto has been paid, the
  off-ramp reaches `failed` and the [refund flow](/guides/use-cases/off-ramp#refunds)
  applies. A decline that is a verdict on the card itself (expired, inactive, cannot
  receive pushes) also flips the card to `rejected` — collect a different card before
  offering a reissue.

Webhooks are the same `payment.*` and `offramp.*` events as any off-ramp. See
[Webhooks](/guides/webhooks).

## Sandbox testing

Sandbox has its own Evervault app ID. Initialise the Card component with it when
targeting `https://sandbox.spritz.finance`; a token encrypted for the production app is
refused there, and the other way round. Card entry accepts any Luhn-valid Visa or
Mastercard number with a future expiry, and the add flow runs against a sandbox card
registration, so the `201`, `403`, `422`, and `400` paths above are all reachable.

As with every sandbox off-ramp, the fiat leg parks at `queued` — no push is submitted, so
`completed`, `failed`, and the `rejected` card state are contract-tested from this guide,
not end-to-end tested. Exercise your handling of them with fixtures. See
[Sandbox](/guides/sandbox) for the environment as a whole.

## Integration checklist

* [ ] Card number is captured only inside the Evervault Card component, with the team ID
  and the app ID for the environment you are calling
* [ ] Your backend forwards the encrypted token and never logs it, and your frontend never
  posts it to Spritz directly (that would expose your integrator key)
* [ ] `expiryMonth` and `expiryYear` are forwarded as the component returns them, not
  re-formatted
* [ ] Cardholder name is collected as first and last name, as printed on the card
* [ ] `403` and `422` on card add produce specific user guidance, and `503` offers
  "try a different card" rather than a retry
* [ ] Quotes use `rail: "push_to_card"` with the card `id` as `accountId`
* [ ] A `rejected` card is hidden from the destination picker and the user is prompted to
  add another

## Related

<CardGroup cols={2}>
  <Card title="Off-ramp: crypto to bank" icon="arrow-right-from-bracket" href="/guides/use-cases/off-ramp">
    The full off-ramp lifecycle this guide builds on.
  </Card>

  <Card title="Onboarding" icon="user-check" href="/guides/onboarding">
    Reading capabilities to know when push to card is active.
  </Card>

  <Card title="End-user pricing" icon="receipt" href="/guides/pricing">
    Fee tiers by rail, including push to card.
  </Card>

  <Card title="Sandbox" icon="flask" href="/guides/sandbox">
    Test users, bypasses, and what the sandbox can and cannot simulate.
  </Card>
</CardGroup>
