JavaScript SDK
SDK 2.1.1What it is#
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#
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#
- Credentials. There are no shared sandbox credentials: get yours through developer registration.
- SDK token. Your backend requests it with
POST /api/v1/loginSdk. See authentication. - Load the script. See installation.
- Add the inputs with the contract ids and the
responseTilopaycontainer. See form fields. - Start the purchase with
Tilopay.Init()and show the customer the payment methods it returns. - Close the purchase with
Tilopay.startPayment(). The SDK handles 3DS and renders the result on yourredirectURL. - 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.
Method flow#
SINPE Móvil: confirmation and timing#
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.
Required: Init() ──────────────────────────────► startPayment()
Optional: getCardType() (show the brand icon)
updateOptions() (if data changes)You can also accept Apple Pay: the SDK draws the button inside the form.
Confirm the result on your server#
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.
code = 1means 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/consultfrom your server. OrderHashis a unique string per transaction: its algorithm is not public and is requested by writing tosoporte@tilopay.com.
Before assuming you are in test mode#
Testing and production share one host: the mode is verified with the environment field returned
by Init() ("TEST" or "PROD"). See environments.
Full example#
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().
<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#
Card data does not pass through your server, but your page captures it: check the applicable scope with your compliance team.
Last verified: 2026-10-22