Ofrece tarjetas ACR Card desde tu propio sitio.
Pega una etiqueta <script> y tus usuarios podrán solicitar cualquiera de las tres tarjetas sin salir de tu página. Si prefieres tu propia interfaz, la misma operación está disponible como API REST.
Qué es esto
ACR Card se solicita, no se emite al instante: el visitante envía sus datos, nuestro equipo revisa la solicitud y le manda las instrucciones para completar su tarjeta. Lo que integras es esa toma de solicitud.
1. El visitante elige tarjeta y rellena sus datos en el widget
2. El widget llama a POST /v1/card-requests con tu clave pública
3. Recibes el id de la solicitud (req_…) y el visitante ve la confirmación
4. Nuestro equipo la revisa y le envía las instrucciones en 24–72 h
5. Tú consultas el estado cuando quieras con tu clave secretaTienes dos formas de integrarlo, y puedes combinarlas:
El widget
Una línea de HTML. Trae el diseño, la validación, los tres niveles y el consentimiento resueltos. Es lo que recomendamos: se integra en una tarde.
La API REST
Tu propio formulario contra nuestro endpoint. Control total del diseño, pero la validación, el consentimiento y su evidencia pasan a ser cosa tuya.
Antes de integrar
Escríbenos a info@acr-pay.com para que demos de alta tu empresa como partner. Te devolvemos tu par de claves y damos de alta los dominios desde los que vas a llamar. Necesitamos además firmar un contrato de tratamiento de datos: vas a enviarnos datos personales de tus usuarios, incluido su documento de identidad.
Instalar el formulario en tu sitio
Una etiqueta <script> y un contenedor. El widget se monta como un iframe servido desde nuestro dominio, con el mismo diseño que la página de ACR Card, y hace todo el trámite dentro de tu sección: elegir tarjeta, rellenar los datos y recibir la confirmación.
<div id="acr-card"></div>
<script src="https://acr-pay.com/embed.js"
data-key="acr_pk_TU_CLAVE_PUBLICA"
data-target="#acr-card"></script>Por qué un iframe y no un fragmento de HTML
El visitante teclea aquí su documento de identidad. Al vivir en un iframe de otro origen, el JavaScript de tu página no puede leer esos campos ni lo que se escribe en ellos. Es lo que nos permite firmar que los datos personales de tus usuarios no pasan por tu sitio — y lo que simplifica el contrato de tratamiento de datos entre tu empresa y la nuestra.
Así se ve
Esto es el widget real, funcionando. Puedes recorrer el flujo entero — elegir una de las tres tarjetas, rellenar los datos y enviar. En esta página el envío no llega a la API y no crea ninguna solicitud.
Tres tarjetas disponibles.
MICHAEL OWEN
ACR Card
InsigniaLa pieza de la casa, en laca azul noche.
- Pagos internacionales y nacionales
- Pagos sin límite y tarjeta personalizada
- Transferencias entre tarjetas
- Transferencias bancarias
Física próximamente
5412 •••• •••• 0011
VIP
PremiumPara quien la usa a menudo y mueve más volumen.
- Pagos internacionales
- Pagos sin límite de importe
- Apple Pay y Google Pay
- Tarjeta personalizada
Física próximamente
Daily
EstándarLa tarjeta del día a día, para el gasto corriente.
- Pagos del día a día
- Red VISA internacional
- Saldo y recargas en la app
- Seguridad en cada operación
Física próximamente
Gestionado por ACR Card
Opciones del widget
Todas se pueden pasar como atributos data-* en la etiqueta del script, o como propiedades del objeto de ACRCard.mount().
| Campo | Tipo | Descripción |
|---|---|---|
data-keyobligatorio | string | Tu clave pública (acr_pk_…). Es visible en el HTML: no uses aquí la clave secreta. |
data-target | selector CSS | Dónde montar el widget. Si se omite, se monta justo donde está la etiqueta <script>. |
data-tier | 'acr' | 'vip' | 'daily' | Tarjeta preseleccionada al abrir. Por defecto acr. El visitante puede cambiarla. |
data-lang | 'es' | 'en' | Idioma del widget. Por defecto es. |
data-accent | color hex | Color de acento de botones y filetes, para casarlo con tu sitio. Los colores propios de cada tarjeta no cambian. |
data-min-height | number | Alto inicial en píxeles antes de la primera medición. Por defecto 420. |
Si necesitas controlar cuándo y dónde se monta —por ejemplo dentro de una pestaña que se abre bajo demanda, o en una aplicación de una sola página— usa el montaje manual:
<script src="https://acr-pay.com/embed.js"></script>
<script>
const widget = ACRCard.mount('#acr-card', {
key: 'acr_pk_TU_CLAVE_PUBLICA',
tier: 'vip', // tarjeta preseleccionada
lang: 'es',
accent: '#1a5a85', // color de acento, para casarlo con tu sitio
onStep(step) { console.log('paso:', step) },
onComplete(data) { console.log('solicitud enviada:', data.cardType) },
})
// widget.destroy() lo retira y libera el escuchador de mensajes
</script>Sobre el color de acento
accent cambia botones y filetes para que el bloque case con tu sitio. No cambia los colores de las tarjetas: la ACR Card es azul noche y la VIP oro porque son la marca del producto, no decoración. Tampoco aceptamos CSS libre — el widget tiene que verse igual y funcionar igual en todas partes.
Medir tu embudo
El widget publica lo que ocurre dentro para que puedas medirlo con tu propia analítica. Los eventos se emiten sobre el contenedor como CustomEvent, y también puedes recibirlos por las funciones onStep y onComplete del montaje manual.
const contenedor = document.querySelector('#acr-card')
contenedor.addEventListener('acrcard:step', (e) => {
// e.detail.step → 'tier' | 'form' | 'success'
gtag('event', 'acr_paso', { paso: e.detail.step })
})
contenedor.addEventListener('acrcard:submitted', (e) => {
// e.detail.cardType → 'acr' | 'vip' | 'daily'
// e.detail.duplicate → true si ya existía una solicitud igual
gtag('event', 'acr_solicitud', { tarjeta: e.detail.cardType })
})Qué NO sale del iframe
En estos eventos viajan solo la altura del contenido, el nombre del paso y qué tarjeta se pidió. Nunca el nombre, el email, el teléfono ni el documento del solicitante. Si tu integración necesita saber quién solicitó, se consulta desde tu servidor con la clave secreta — nunca desde el navegador.
Política de seguridad de contenido
Si tu sitio envía una cabecera Content-Security-Policy, hay que autorizar nuestro dominio o el widget no cargará. Es el motivo más común de que una integración correcta parezca no funcionar, y el navegador solo lo dice en la consola.
Content-Security-Policy:
script-src 'self' https://acr-pay.com;
frame-src 'self' https://acr-pay.com;Cookies de terceros
El widget no usa cookies. El estado entre pasos vive en memoria del iframe, porque los navegadores bloquean el almacenamiento de terceros y una integración apoyada en cookies se rompería sola en Safari y en Chrome con protección reforzada.
Dos claves, y la diferencia importa
Cada partner recibe un par de claves. Se envían siempre en la cabecera Authorization: Bearer ….
acr_pk_…Pública
Va en el HTML de tu web. No es un secreto: cualquiera puede leerla en tu código fuente. Lo único que la protege es la lista de dominios autorizados. Solo puede crear solicitudes.
acr_sk_…Secreta
Solo en tu servidor, en una variable de entorno. Además de crear, puede consultar y listar. Nunca la pongas en el navegador ni la subas al repositorio.
Si la clave secreta se filtra
Revócala desde tu panel de partner y emite otra. Guardamos solo su huella criptográfica, así que no podemos recuperártela ni decirte cuál era: la única salida es sustituirla.
Los dominios de la clave pública se comparan de forma exacta —esquema, dominio y puerto— y no admiten comodines. Si sirves en https://tusitio.com y https://www.tusitio.com, autoriza los dos.
Crear una solicitud
/api/v1/card-requestsclave pública o secretaEs el único endpoint que acepta la clave pública, porque es el que usa el widget desde el navegador del visitante.
| Campo | Tipo | Descripción |
|---|---|---|
nameobligatorio | string | Nombre del solicitante. Máximo 80 caracteres. |
surnameobligatorio | string | Apellidos. Máximo 80 caracteres. |
documentobligatorio | string | DNI o pasaporte. Alfanumérico, de 5 a 20 caracteres. |
emailobligatorio | string | Correo del solicitante. Se normaliza a minúsculas. |
phoneobligatorio | string | Teléfono en formato internacional con prefijo: +34600111222. |
card_typeobligatorio | 'acr' | 'vip' | 'daily' | Qué tarjeta se solicita. Consulta /v1/tiers para el catálogo vigente. |
external_ref | string | Tu propio identificador para este trámite. Si lo envías, reintentar la misma petición devuelve la solicitud ya creada en vez de duplicarla. |
source_url | string | URL de la página desde la que se solicitó. El widget lo rellena solo. |
curl https://acr-pay.com/api/v1/card-requests \
-H "Authorization: Bearer acr_sk_TU_CLAVE_SECRETA" \
-H "Content-Type: application/json" \
-d '{
"name": "Ana",
"surname": "Pérez Gómez",
"document": "X1234567Z",
"email": "ana@ejemplo.com",
"phone": "+34600111222",
"card_type": "acr",
"external_ref": "pedido-4821"
}'{
"id": "req_9f2a4c8e1b7d3a5f6c0e2d41",
"object": "card_request",
"status": "pending",
"card_type": "acr",
"cardholder": {
"name": "Ana",
"surname": "Pérez Gómez",
"email": "ana@ejemplo.com",
"phone": "+34600111222"
},
"external_ref": "pedido-4821",
"source_url": "https://tu-sitio.com/tarjetas",
"created_at": "2026-09-01T10:24:11.482Z",
"duplicate": false,
"request_id": "req_trace_4b1c9e02aa73f5d8"
}Reintentar sin duplicar
Si envías external_ref, repetir la misma petición tras un fallo de red devuelve la solicitud original con duplicate: true y un 200, en lugar de crear una segunda. Usa el identificador que ya tengas en tu sistema para ese trámite.
Aunque no lo envíes, deduplicamos por persona y nivel: la misma tarjeta para el mismo email o teléfono es siempre una sola solicitud. Si esa solicitud previa la creó otro partner, la respuesta lo indica con status: "duplicate" pero devuelve id: null — no exponemos los identificadores de un partner a otro.
const res = await fetch('https://acr-pay.com/api/v1/card-requests', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.ACR_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name, surname, document, email, phone,
card_type: 'acr',
external_ref: pedidoId, // tu identificador: hace el reintento seguro
}),
})
const solicitud = await res.json()
if (!res.ok) throw new Error(solicitud.error.message)<?php
$ch = curl_init('https://acr-pay.com/api/v1/card-requests');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('ACR_SECRET_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => $nombre,
'surname' => $apellidos,
'document' => $documento,
'email' => $email,
'phone' => $telefono,
'card_type' => 'acr',
'external_ref' => $pedidoId,
]),
]);
$solicitud = json_decode(curl_exec($ch), true);Consultar y listar
/api/v1/card-requests/:idsolo clave secreta/api/v1/card-requestssolo clave secretaEstas dos devuelven datos personales de tus solicitantes, así que exigen la clave secreta: servirlas a una clave pública las dejaría a la vista de cualquiera que abriese el código fuente de tu página. La consulta filtra siempre por tu partner — nunca podrás leer una solicitud ajena, aunque aciertes el identificador.
| Campo | Tipo | Descripción |
|---|---|---|
limit | number | Cuántas devolver, de 1 a 100. Por defecto 25. |
status | string | Filtra por estado: pending, approved o rejected. |
card_type | string | Filtra por nivel de tarjeta. |
starting_after | string | Cursor de la página siguiente. Usa el next_cursor de la respuesta anterior. |
curl "https://acr-pay.com/api/v1/card-requests?limit=25&status=pending" \
-H "Authorization: Bearer acr_sk_TU_CLAVE_SECRETA"{
"object": "list",
"data": [ { "id": "req_9f2a…", "status": "pending", … } ],
"has_more": true,
"next_cursor": "Y3JfNDgyMQ",
"request_id": "req_trace_1a0f…"
}La paginación es por cursor: si has_more es true, pasa el next_cursor como starting_after en la siguiente llamada. No uses desplazamiento numérico: entran solicitudes nuevas mientras paginas y con offset se repiten o se saltan filas.
El documento de identidad no se devuelve nunca
Ninguna respuesta incluye el campo document, ni siquiera con la clave secreta. Todo el sentido de que el formulario viva en un iframe es que ese dato no pasa por tus sistemas; devolverlo por la API lo entregaría igualmente por la puerta de atrás.
Estados de una solicitud
| Campo | Tipo | Descripción |
|---|---|---|
pending | inicial | Recibida, pendiente de revisión por nuestro equipo. |
approved | final | Aprobada. Al solicitante se le envían las instrucciones para completar su tarjeta. |
rejected | final | Rechazada tras la revisión. |
duplicate | respuesta | No es un estado almacenado: aparece al crear cuando ya existe una solicitud igual de otro partner. |
Cómo enterarte de un cambio de estado
Hoy se consulta con GET /v1/card-requests/:id. Los webhooks firmados (card_request.approved y card_request.rejected) están en camino; hasta entonces, una consulta diaria de las solicitudes en pending es suficiente para el volumen que maneja cualquier partner.
Las tarjetas disponibles
/api/v1/tiers?lang=esclave pública o secretaSi prefieres pintar tu propia selección de tarjetas en vez de usar el widget, léelas de aquí. No las copies a tu plantilla: el día que cambie una recarga mínima, tu sitio seguiría anunciando la antigua y nadie se daría cuenta.
| Tarjeta | id | Recarga mínima | Física | Estado |
|---|---|---|---|---|
ACR CardInsignia | acr | 50 USD | Próximamente · 25 USD | Disponible |
VIPPremium | vip | 5.000 USD | Próximamente · 50 USD | Disponible |
DailyEstándar | daily | 100 USD | Próximamente · 30 USD | Disponible |
La tarjeta física todavía no se pide por API
Las tres tarjetas se solicitan hoy en formato virtual. El plástico existe y tiene precio, pero pedirlo requiere formato y dirección de envío, que aún no están en el modelo de datos. Cuando lleguen serán campos opcionales nuevos: el contrato de la API no cambia y tu integración seguirá funcionando sin tocarla.
Cómo vienen los fallos
Todos los errores tienen la misma forma. Escribe tu lógica contra code, que es estable, y no contra message, que podemos reescribir o traducir.
{
"error": {
"type": "invalid_request_error",
"code": "invalid_parameter",
"message": "El teléfono debe ir en formato internacional, por ejemplo +34600111222.",
"param": "phone"
},
"request_id": "req_trace_9d02f7b1c4e6a380"
}| code | HTTP | Qué significa |
|---|---|---|
missing_api_key | 401 | No enviaste la cabecera Authorization. |
invalid_api_key | 401 | La clave no existe o fue revocada. |
origin_not_allowed | 403 | El dominio desde el que llama el navegador no está en la lista de tu clave pública. |
secret_key_required | 403 | Esa operación necesita la clave secreta; enviaste la pública. |
partner_suspended | 403 | Tu cuenta de partner está suspendida. |
invalid_parameter | 400 | Un campo no es válido. El campo concreto viene en param. |
invalid_json | 400 | El cuerpo no es JSON válido. |
resource_missing | 404 | No existe ninguna solicitud tuya con ese identificador. |
too_many_requests | 429 | Superaste las 300 peticiones por minuto de esa clave. |
internal_error | 500 | Fallo nuestro. Reintenta y escríbenos con el request_id. |
request_id
Toda respuesta, correcta o no, incluye un request_id. Guárdalo en tus registros: es lo que nos permite localizar tu petición exacta si algo va mal, en vez de buscar a ciegas por fecha y hora.
El límite es de 300 peticiones por minuto y clave. Un widget en una página con tráfico normal no se acerca; si lo superas de forma sostenida, escríbenos y lo ajustamos.

