# JavaScript SDK

> The integration path where the card form lives on your page and the SDK sends the data straight to Tilopay.

- kind: guide
- status: stable
- sdk_version: 2.1.1
- last_verified: 2026-10-22
- url: https://tilopay.com/en/developers/sdk

## What it is [#que-es]

The card fields live on your page. The V2 SDK takes over those inputs and sends the data
**from the browser straight to Tilopay**: it never passes through your server.

## Who it is for [#para-quien]

For teams that want to control the checkout design without card data touching their backend.
You need a JavaScript frontend and a minimal backend to obtain the SDK token.

## How to build it, in order [#orden]

1. **Credentials.** There are no shared sandbox credentials: get yours through
   [developer registration](/en/developers/registro).
2. **SDK token.** Your backend requests it with `POST /api/v1/loginSdk`. See
   [authentication](/en/developers/api/autenticacion).
3. **Load the script.** See [installation](/en/developers/sdk/instalacion).
4. **Add the inputs with the contract ids** and the `responseTilopay` container. See
   [form fields](/en/developers/sdk/campos-del-formulario).
5. **Start the purchase** with [`Tilopay.Init()`](/en/developers/sdk/reference/init) and show the
   customer the payment methods it returns.
6. **Close the purchase** with
   [`Tilopay.startPayment()`](/en/developers/sdk/reference/startpayment). The SDK handles 3DS and
   renders the result on your `redirect` URL.
7. **Confirm the result on your server.** The redirect belongs to the customer; before
   delivering the product, confirm the status with the API. See
   [confirm the result](#confirmar).

## Method flow [#flujo]

## SINPE Móvil: confirmation and timing [#sinpe-confirmacion]

When the transfer arrives, the SDK redirects to your `redirect`; Tilopay also notifies `webhook` if provided. The SDK does not set a deadline, but Tilopay matches transfers with SINPE Móvil orders created within the last **25 minutes**: customers must transfer within that window. The session depends on the SDK token lifetime.

```text
Required:    Init()  ──────────────────────────────►  startPayment()
Optional:            getCardType()   (show the brand icon)
                     updateOptions() (if data changes)
```

You can also accept [Apple Pay](/en/developers/sdk/apple-pay): the SDK draws the button inside the
form.

## Confirm the result on your server [#confirmar]

When it finishes, the customer's browser lands on your `redirect` URL with the result
parameters: `code`, `description`, `auth`, `order`, `tpt`, `tilopay-transaction`, `OrderHash`
and `returnData`. See the full table in
[parameters that arrive at the redirect](/en/developers/api/hosted-payment-page/process-payment#parametros-del-redirect).

- `code = 1` means the transaction is approved; any other value is a decline.
- The redirect is received by the customer's browser, so it is not enough proof: **before
  delivering the product, confirm the status with `POST /api/v1/consult` from your server**.
- `OrderHash` is a unique string per transaction: its algorithm is not public and is
  requested by writing to `soporte@tilopay.com`.

## Before assuming you are in test mode [#entornos]

Testing and production share one host: the mode is verified with the `environment` field returned
by `Init()` (`"TEST"` or `"PROD"`). See [environments](/en/developers/entornos).

## Full example [#ejemplo-completo]

Form inside `.payFormTilopay`, the five fields, Pay and Update buttons, `responseTilopay`
outside the form, the v2 script, `Init()` with every field, loading `methods` and `cards` into
their selects, and `startPayment()`.

```html
<div class="payFormTilopay">
  <select id="tlpy_payment_method" name="tlpy_payment_method"></select>
  <select id="tlpy_saved_cards" name="tlpy_saved_cards"></select>
  <input type="text" id="tlpy_cc_number" name="tlpy_cc_number" />
  <input type="text" id="tlpy_cc_expiration_date" name="tlpy_cc_expiration_date" placeholder="12/30" />
  <input type="text" id="tlpy_cvv" name="tlpy_cvv" />
  <button type="button" id="pagar">Pagar</button>
  <button type="button" id="actualizar">Actualizar</button>
</div>

<div id="responseTilopay"></div>

<script src="https://app.tilopay.com/sdk/v2/sdk_tpay.min.js"></script>
<script>
  async function iniciar() {
    const init = await Tilopay.Init({
      token: "<token_del_sdk>",
      currency: "CRC",
      language: "es",
      amount: 100.0,
      billToEmail: "cliente@ejemplo.com",
      orderNumber: "ORD-2026-000123",
      typeDni: 1,
      dni: "0707770777",
      billToFirstName: "Ana",
      billToLastName: "Rojas",
      billToAddress: "Avenida 1, Local 2",
      billToAddress2: "",
      billToCity: "San José",
      billToState: "SJ",
      billToZipPostCode: "10101",
      billToCountry: "CR",
      billToTelephone: "88888888",
      shipToFirstName: "Ana",
      shipToLastName: "Rojas",
      shipToAddress: "Avenida 1, Local 2",
      shipToAddress2: "",
      shipToCity: "San José",
      shipToState: "SJ",
      shipToZipPostCode: "10101",
      shipToCountry: "CR",
      shipToTelephone: "88888888",
      capture: 1,
      redirect: "https://ejemplo.com/respuesta",
      subscription: 0,
      hashVersion: "V2",
      returnData: "",
    });

    if (init.environment !== "TEST") console.warn("Init():", init.environment);

    const methods = document.getElementById("tlpy_payment_method");
    init.methods.forEach((m) => methods.add(new Option(m.name, m.id)));

    const cards = document.getElementById("tlpy_saved_cards");
    init.cards.forEach((c) => cards.add(new Option(c.name, c.id)));
  }

  document.getElementById("tlpy_cc_number").addEventListener("change", async () => {
    const brand = await Tilopay.getCardType();
    console.log(brand.message); // "visa", "mastercard", "amex"
  });

  document.getElementById("actualizar").addEventListener("click", async () => {
    await Tilopay.updateOptions({ billToTelephone: "87777777" });
  });

  document.getElementById("pagar").addEventListener("click", async () => {
    const payment = await Tilopay.startPayment();
    if (payment && payment.message) console.error(payment.message);
  });

  iniciar();
</script>
```

## Compliance [#cumplimiento]

Card data does not pass through your server, but your page captures it: check the applicable
scope with your compliance team.
