n1coePayDocs

Get started

Card capture

Save a buyer's card to charge it later.

≈ 10 minutes · curl, Node.js, .NET, PHP or Java · n1co.js, Web or React Native

Step 1Create a card session on your server

Your server creates the session for the buyer and gives the page only the session_token. It is good for one card, for 15 minutes.

Your secret_key goes in the Authorization header and never leaves your server.

curl

POST /v1/card_sessions
curl -s -X POST https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com/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"]
  }'

Node.js

server.mjs
app.post('/checkout/card-session', async (req, res) => {
  // The buyer comes from your user session, never from the browser.
  const response = await fetch('https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com/v1/card_sessions', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.N1CO_SECRET_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      customer: { id: 'cust-1001', name: 'Ana López', email: 'ana.lopez@example.com', phone: '+503 7000-1234' },
      page_origins: ['https://shop.example.com'],
    }),
  });
  if (!response.ok) throw new Error(`n1co ${response.status}: ${await response.text()}`);
  const session = await response.json();
  res.json({ session_token: session.session_token });
});

.NET

Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient("n1co", http =>
{
    http.BaseAddress = new Uri("https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com");
    http.DefaultRequestHeaders.Authorization =
        new("Bearer", Environment.GetEnvironmentVariable("N1CO_SECRET_KEY"));
});
var app = builder.Build();

app.MapPost("/checkout/card-session", async (IHttpClientFactory factory) =>
{
    // The buyer comes from your user session, never from the browser.
    var response = await factory.CreateClient("n1co").PostAsJsonAsync("/v1/card_sessions", new
    {
        customer = new { id = "cust-1001", name = "Ana López", email = "ana.lopez@example.com", phone = "+503 7000-1234" },
        page_origins = new[] { "https://shop.example.com" },
    });
    response.EnsureSuccessStatusCode();
    var session = await response.Content.ReadFromJsonAsync<System.Text.Json.JsonElement>();
    return Results.Ok(new { session_token = session.GetProperty("session_token").GetString() });
});

PHP

card-session.php
<?php

// The buyer comes from your user session, never from the browser.
$ch = curl_init('https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com/v1/card_sessions');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
        'customer' => ['id' => 'cust-1001', 'name' => 'Ana López', 'email' => 'ana.lopez@example.com', 'phone' => '+503 7000-1234'],
        'page_origins' => ['https://shop.example.com'],
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('N1CO_SECRET_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($response === false || $status >= 300) {
    throw new RuntimeException("n1co $status: $response");
}
$session = json_decode($response, true);
header('Content-Type: application/json');
echo json_encode(['session_token' => $session['session_token']]);

PHP (SDK)

card-session.php
<?php

use N1co\EPay\N1co;

$n1co = new N1co((string) getenv('N1CO_SECRET_KEY'), ['base_url' => 'https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com']);

// The buyer comes from your user session, never from the browser.
$session = $n1co->cardSessions->create([
    'customer' => ['id' => 'cust-1001', 'name' => 'Ana López', 'email' => 'ana.lopez@example.com', 'phone' => '+503 7000-1234'],
    'page_origins' => ['https://shop.example.com'],
]);
header('Content-Type: application/json');
echo json_encode(['session_token' => $session->session_token]);

Java

CardSessionController.java
// Java 21+, Spring Boot and Jackson.
@RestController
class CardSessionController {
  private final HttpClient http = HttpClient.newHttpClient();
  private final ObjectMapper json = new ObjectMapper();

  @PostMapping("/checkout/card-session")
  Map<String, Object> cardSession() throws Exception {
    // The buyer comes from your user session, never from the browser.
    var body = Map.of(
        "customer", Map.of("id", "cust-1001", "name", "Ana López", "email", "ana.lopez@example.com", "phone", "+503 7000-1234"),
        "page_origins", List.of("https://shop.example.com"));
    var request = HttpRequest.newBuilder(URI.create("https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com/v1/card_sessions"))
        .header("Authorization", "Bearer " + System.getenv("N1CO_SECRET_KEY"))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(json.writeValueAsString(body)))
        .build();
    var response = http.send(request, HttpResponse.BodyHandlers.ofString());
    if (response.statusCode() >= 300) {
      throw new IllegalStateException("n1co " + response.statusCode() + ": " + response.body());
    }
    var session = json.readValue(response.body(), Map.class);
    return Map.of("session_token", session.get("session_token"));
  }
}
201Created
{
  "id": "cs_U2Vzc2lvbkV4YW1wbGUwMDAx",
  "session_token": "cs_U2Vzc2lvbkV4YW1wbGUwMDAx_token_U2Vzc2lvblRva2VuRXhhbXBsZTAwMDAw",
  "mode": "capture",
  "card": null,
  "status": "requires_card",
  "attempts_remaining": 5,
  "page_origins": ["https://shop.example.com"],
  "expires_at": "2026-10-16T15:19:05Z"
}
Field Values Default Purpose
page_origins 1 to 5 https://… origins; http://localhost and http://127.0.0.1 for development Optional Only those pages can use the token.

Step 2Capture the card on your page

Mount the card element and call capture() with the session_token. Card data is typed into an n1co iframe and never passes through your page.

HTML

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.com/v1/n1co.js"></script>
    <script>
      const card = n1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA', { environment: 'sandbox' }).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 session_token.
          const { session_token } = await fetch('/checkout/card-session', { method: 'POST' }).then((r) => r.json());
          const result = await card.capture({ session_token });
          // result.card: { id, brand, last4, exp_month, exp_year }
          message.textContent = `We saved your card ending in ${result.card.last4}.`;
        } catch (error) {
          message.textContent = error.message;
          save.disabled = false;
        }
      });
    </script>
  </body>
</html>

Web

Terminal
npm install @n1co/elements
pnpm add @n1co/elements
yarn add @n1co/elements
bun add @n1co/elements
save-card.ts
import { loadN1co } from '@n1co/elements';

// null during server-side rendering: call it in the browser.
const client = await loadN1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA', { environment: 'sandbox' });
if (client) {
  const card = client.elements().create('card', { locale: 'en' });
  card.mount('#card');

  document.querySelector('#save')!.addEventListener('click', async () => {
    const { session_token } = await fetch('/checkout/card-session', { method: 'POST' }).then((r) => r.json());
    try {
      const result = await card.capture({ session_token });
      // result.card: { id, brand, last4, exp_month, exp_year }
      document.querySelector('#card-error')!.textContent = '';
    } catch (error) {
      // error.code is stable; error.message is localized and safe to show.
      document.querySelector('#card-error')!.textContent = (error as Error).message;
    }
  });
}

Vue / Nuxt

Terminal
npm install @n1co/elements
pnpm add @n1co/elements
yarn add @n1co/elements
bun add @n1co/elements
SaveCard.vue
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue';
import { loadN1co, type Element as CardElement } from '@n1co/elements';

const cardBox = ref<HTMLElement | null>(null);
const card = ref<CardElement | null>(null);
const message = ref('');
const complete = ref(false);
const saving = ref(false);

onMounted(async () => {
  // null during server-side rendering; in Nuxt you can also wrap this component in <ClientOnly>.
  const client = await loadN1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA', { environment: 'sandbox' });
  if (!client || !cardBox.value) return;
  card.value = client.elements().create('card', { locale: 'en' });
  card.value.on('change', (e) => {
    complete.value = e.complete;
    message.value = e.error ? e.error.message : '';
  });
  card.value.on('error', (e) => {
    message.value = e.message;
  });
  card.value.mount(cardBox.value);
});

onBeforeUnmount(() => card.value?.unmount());

async function save() {
  if (!card.value) return;
  saving.value = true;
  try {
    // Your endpoint creates the session with your secret_key and returns only its session_token.
    const { session_token } = await fetch('/checkout/card-session', { method: 'POST' }).then((r) => r.json());
    const result = await card.value.capture({ session_token });
    // result.card: { id, brand, last4, exp_month, exp_year }
    message.value = `We saved your card ending in ${result.card.last4}.`;
  } catch (error) {
    message.value = error instanceof Error ? error.message : String(error);
    saving.value = false;
  }
}
</script>

<template>
  <form @submit.prevent="save">
    <div ref="cardBox"></div>
    <p role="alert" v-text="message"></p>
    <button type="submit" :disabled="!complete || saving">Save card</button>
  </form>
</template>

React Native

Terminal
npm install @n1co/react-native react-native-webview react-native-get-random-values
# Expo: npx expo install react-native-webview expo-crypto react-native-get-random-values
pnpm add @n1co/react-native react-native-webview react-native-get-random-values
# Expo: pnpm expo install react-native-webview expo-crypto react-native-get-random-values
yarn add @n1co/react-native react-native-webview react-native-get-random-values
# Expo: yarn expo install react-native-webview expo-crypto react-native-get-random-values
bun add @n1co/react-native react-native-webview react-native-get-random-values
# Expo: bun expo install react-native-webview expo-crypto react-native-get-random-values
SaveCard.tsx
import 'react-native-get-random-values'; // first line of your app
import { useRef, useState } from 'react';
import { Button } from 'react-native';
import { CardField, N1coProvider, type CardFieldHandle } from '@n1co/react-native';

// From an app, your server creates the session without page_origins: the page that captures is n1co's.
export function App() {
  return (
    <N1coProvider publishableKey="pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA" environment="sandbox" locale="en">
      <SaveCard />
    </N1coProvider>
  );
}

function SaveCard() {
  const card = useRef<CardFieldHandle>(null);
  const [complete, setComplete] = useState(false);

  async function save() {
    const { session_token } = await fetch('https://shop.example.com/checkout/card-session', { method: 'POST' }).then(
      (r) => r.json(),
    );
    const { card: saved } = await card.current!.capture({ session_token });
    // saved: { id, brand, last4, exp_month, exp_year }
  }

  return (
    <>
      <CardField ref={card} onChange={(event) => setComplete(event.complete)} />
      <Button title="Save card" disabled={!complete} onPress={save} />
    </>
  );
}
Option Values Default Where
environment 'production', 'sandbox' 'production' n1co(), loadN1co(), N1coProvider
locale 'es', 'en' 'es' create('card', …), N1coProvider

Methods and options of each SDK: Web and React Native.

Step 3Confirm with the webhook

Your server saves the card when n1co notifies it of the capture, with a valid signature; the check is in Set up webhooks. You can also read the session: it is completed and card is the card's id.

That id is the payment_method you charge the card with in Payment intents.

Node.js

server.mjs
import express from 'express';
import { verifyWebhook } from './webhooks.mjs';

// express.raw: the signature covers the raw body, before express.json().
app.post('/webhooks/n1co', express.raw({ type: 'application/json', limit: '1mb' }), (req, res) => {
  if (!verifyWebhook(req.headers, req.body)) return res.sendStatus(400);
  const event = JSON.parse(req.body);
  if (event.type === 'card.created') {
    // Save event.data.object.id with the buyer of session event.data.object.card_session.
  }
  res.sendStatus(204);
});

.NET

Program.cs
app.MapPost("/webhooks/n1co", async (HttpRequest request) =>
{
    using var buffer = new MemoryStream();
    await request.Body.CopyToAsync(buffer);
    var body = buffer.ToArray();
    var h = request.Headers;
    if (!N1coWebhook.Verify(h["webhook-id"], h["webhook-timestamp"], h["webhook-signature"], body))
        return Results.BadRequest();
    using var evt = System.Text.Json.JsonDocument.Parse(body);
    if (evt.RootElement.GetProperty("type").GetString() == "card.created")
    {
        // Save data.object.id with the buyer of session data.object.card_session.
    }
    return Results.NoContent();
});

PHP

webhook-endpoint.php
<?php

require __DIR__ . '/webhooks.php';

$body = file_get_contents('php://input');
if (!verify_webhook($body)) {
    http_response_code(400);
    exit;
}
$event = json_decode($body, true);
if ($event['type'] === 'card.created') {
    // Save $event['data']['object']['id'] with the buyer of session $event['data']['object']['card_session'].
}
http_response_code(204);

PHP (SDK)

webhook-endpoint.php
<?php

require __DIR__ . '/webhooks.php'; // $event, verified by the SDK

if ($event->type === 'card.created') {
    // Save $event->data->object->id with the buyer of session $event->data->object->card_session.
}
http_response_code(204);

Java

WebhookController.java
@RestController
class WebhookController {
  private final ObjectMapper json = new ObjectMapper();

  @PostMapping("/webhooks/n1co")
  ResponseEntity<Void> receive(@RequestHeader HttpHeaders headers, @RequestBody byte[] body) throws Exception {
    if (!N1coWebhook.verify(headers.getFirst("webhook-id"), headers.getFirst("webhook-timestamp"),
        headers.getFirst("webhook-signature"), body)) {
      return ResponseEntity.badRequest().build();
    }
    var event = json.readValue(body, Map.class);
    if ("card.created".equals(event.get("type"))) {
      // Save data.object.id with the buyer of session data.object.card_session.
    }
    return ResponseEntity.noContent().build();
  }
}
GET /v1/card_sessions/{id}
curl -s https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com/v1/card_sessions/cs_U2Vzc2lvbkV4YW1wbGUwMDAx \
  -H "Authorization: Bearer $N1CO_SECRET_KEY"

You saved your buyer's card.

Charge it whenever you want with its id.

Type to search the documentation.