n1coePayDocs API reference

Save a card

Save a buyer's card with a card session and the n1co.js card element, without the data passing through your page.

  1. Your server creates a card session with POST /v1/card_sessions and hands its client_secret to the page.
  2. Your page shows the card element and calls capture({ client_secret }).
  3. Your server confirms the card with the session or with the card.created webhook.

First, your server creates the session:

Create a card session
curl -s -X POST https://api-epay.dev.n1co.dev/v1/card_sessions \
  -H "Authorization: Bearer $N1CO_SECRET_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "customer": {
      "id": "cust-1001",
      "name": "Ana López",
      "email": "ana.lopez@example.com",
      "phone": "+503 7000-1234"
    },
    "page_origins": ["https://shop.example.com"]
  }'
201 Created
{
  "id": "cs_U2Vzc2lvbkV4YW1wbGUwMDAx",
  "client_secret": "cs_U2Vzc2lvbkV4YW1wbGUwMDAx_secret_U2VjcmV0RXhhbXBsZTAwMDAwMDAwMDAw",
  "status": "requires_card",
  "attempts_remaining": 5,
  "card": null,
  "page_origins": ["https://shop.example.com"],
  "expires_at": "2026-10-16T15:19:05Z",
  "created_at": "2026-10-16T15:04:05Z"
}

Then, the page. Replace the publishable key with yours and point both fetch calls at your endpoints:

save-card.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Save card</title>
  </head>
  <body>
    <form id="card-form">
      <div id="card"></div>
      <p id="card-message" role="alert"></p>
      <button id="save" type="submit" disabled>Save card</button>
    </form>

    <script src="https://elements.n1co.dev/v1/n1co.js"></script>
    <script>
      const card = n1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA').elements().create('card', { locale: 'en' });
      const save = document.querySelector('#save');
      const message = document.querySelector('#card-message');

      card
        .on('change', ({ complete, error }) => {
          save.disabled = !complete;
          message.textContent = error ? error.message : '';
        })
        .on('error', (error) => {
          message.textContent = error.message;
        })
        .mount('#card');

      document.querySelector('#card-form').addEventListener('submit', async (event) => {
        event.preventDefault();
        save.disabled = true;
        try {
          // Your endpoint creates the session with your secret key and returns only its client_secret.
          const { client_secret } = await fetch('/checkout/card-session', { method: 'POST' }).then((r) => r.json());
          const result = await card.capture({ client_secret });
          // Your server confirms by reading the session with the secret key.
          await fetch('/checkout/card', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ card: result.card.id }),
          });
          message.textContent = `We saved your card ending in ${result.card.last4}.`;
        } catch (error) {
          message.textContent = error.message;
          save.disabled = false;
        }
      });
    </script>
  </body>
</html>

The page is served over https. For development, http://localhost and http://127.0.0.1 are accepted too.

The customer travels in the request#

customer is an object with your own id for the buyer, their name, their email and their phone. You do not create the customer beforehand: the API creates it the first time it sees that id and, in later calls, updates the other details.

Field Rules
id Your identifier: 1 to 64 characters A-Z a-z 0-9 . _ -.
name 2 to 100 characters.
email A valid email address.
phone E.164 format. Spaces, hyphens and parentheses are accepted.

All four are required. No response or webhook returns the customer.

The session#

The client_secret is shown only when the session is created. It is valid for one card, for 15 minutes and 5 attempts.

With page_origins, the session only works on those pages: 1 to 5 origins such as https://shop.example.com. Always use it in production.

status Meaning
requires_card Waiting for a card.
completed A card was saved; card has its id.
expired Expired without a card.
canceled No attempts left, or the capture could not be confirmed.

What your page must handle#

capture() resolves with { ok: true, card }: the card id, the brand, the last four digits and the expiry date. If it fails, it rejects with a stable code and a message ready to show.

  • An invalid field: show message; the buyer fixes it.
  • card_rejected: ask for another card.
  • The session no longer works (card_session_expired, card_session_canceled and similar): create a new session.
  • capture_timeout or network_error: the card may have been saved. Read the session on your server before retrying.

Every code, option and event of the card element is in the n1co.js reference.

Confirm on your server#

The browser result is a hint. Your server confirms with GET /v1/card_sessions/{id}: the session is completed and card is the card id.

curl -s https://api-epay.dev.n1co.dev/v1/card_sessions/cs_U2Vzc2lvbkV4YW1wbGUwMDAx \
  -H "Authorization: Bearer $N1CO_SECRET_KEY"

You also receive the card.created webhook with the card. Its card_session is the session id (cs_…), which ties the card to the buyer you created the session for:

card.created
{
  "id": "evt_RXZlbnRFeGFtcGxlMDAwMDAx",
  "type": "card.created",
  "created_at": "2026-10-16T15:06:12Z",
  "data": {
    "object": {
      "id": "card_Q2FyZEV4YW1wbGUwMDAwMDAx",
      "card_session": "cs_U2Vzc2lvbkV4YW1wbGUwMDAx",
      "brand": "visa",
      "last4": "4242",
      "exp_month": 12,
      "exp_year": 2030,
      "issuer": { "name": "Banco Ejemplo", "country": "SV" },
      "fingerprint": "5f0c3b1e9a7d4c2b8e6f0a1d3c5b7e9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b",
      "status": "active",
      "created_at": "2026-10-16T15:06:12Z"
    }
  }
}

Store the card id with your user: with it and the same customer.id you charge the card later. If the buyer saves a card they already had, you get the same id and no new card.created arrives.

Cards that stop working#

An active card can be charged. It becomes unavailable when n1co no longer knows it, and goes back to active if the buyer saves it again.

When charging, the API refuses an unavailable card with 409 card_unavailable: ask the buyer to type it again. The card fields are in the API reference.