1. Cómo funciona
- Tu backend crea la suscripción con
POST /subscriptions, indicando el cliente, el importe y tusuccess_url. Recibes uncheckout_url. - Rediriges a tu usuario a ese
checkout_url. Acepta los cargos recurrentes y paga el primer periodo en la página segura de Redsys (con 3D Secure). Nunca vemos ni guardamos los datos de la tarjeta: Redsys nos da una referencia. Si indicaste unfirst_charge_dateposterior a hoy, solo guarda la tarjeta y no paga nada hasta ese día. - Redsys devuelve al usuario a tu
success_urly te llega el avisosubscription.activated. La factura del periodo se emite con Verifactu, se le envía por email y te llegainvoice.paid. - Cada periodo cobramos solos con la tarjeta guardada. Si ese periodo lleva cargos puntuales (consumos, una matrícula...), van en el mismo cargo y en la misma factura. Si el cobro falla, avisamos al cliente con un enlace para pagar con otra tarjeta, reintentamos a los 3 y 7 días y, si a los 10 días no se ha cobrado, la suscripción se suspende. Te avisamos en cada paso para que actives o bloquees la cuenta.
- Para dar de baja,
POST /subscriptions/{id}/cancel. Por defecto la baja es al final del periodo ya pagado.
La factura se emite solo cuando el cobro sale bien: un cobro fallido no genera facturas que haya que anular.
2. Autenticación y modo pruebas
Crea una clave en Mi empresa > API y avisos. Solo se muestra una vez: guárdala en tu servidor, nunca en el navegador ni en una app móvil. Envíala en cada petición:
Authorization: Bearer crk_xxxxxxxxxxxxxxxxxxxxxxxx
Si tu infraestructura no deja pasar la cabecera Authorization, puedes usar X-Api-Key: crk_....
La URL base es https://facturas.crescere.es/api/v1. El modo pruebas sigue al de Redsys de la empresa: si el TPV está en pruebas, los cobros van al entorno de pruebas de Redsys y los avisos llevan "livemode": false. Compruébalo con:
curl https://facturas.crescere.es/api/v1/ping -H "Authorization: Bearer $CRESCERE_KEY"
3. Convenciones
- Peticiones y respuestas en JSON (
Content-Type: application/json). Importes en euros con decimales (9.99), fechas ISO 8601. - Errores: código HTTP y
{"error": {"code": "...", "message": "...", "details": {...}}}.400petición mal formada,401clave ausente o inválida,403plan sin API,404no existe,409conflicto con el estado actual,422datos no válidos (detalle por campo endetails),429demasiadas peticiones. - Idempotencia: en los
POST, envíaIdempotency-Key: <id único>. Si repites la petición (por un timeout, por ejemplo) con la misma clave en 24 h, recibes la misma respuesta y no se crea nada dos veces. - Límite: 120 peticiones por minuto y clave.
external_id: guarda tu propio identificador en clientes y suscripciones para encontrarlos sin guardar los nuestros.
4. Clientes
| Método | Ruta | Qué hace |
|---|---|---|
| POST | /customers | Crea un cliente. Si ya existe uno con el mismo external_id, lo devuelve (200 en lugar de 201). |
| GET | /customers/{id} | Datos del cliente. |
Campos: fiscal_name*, nif* (NIF/CIF válido si el país es ES), email* (recibe las facturas), email_billing, address, postal_code, city, province, country_code (por defecto ES), type (company o individual), external_id.
curl -X POST https://facturas.crescere.es/api/v1/customers \
-H "Authorization: Bearer $CRESCERE_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: alta-cliente-4521" \
-d '{"fiscal_name":"Ejemplo SL","nif":"B12345678","email":"facturas@ejemplo.es","external_id":"acc_4521"}'
5. Suscripciones
| Método | Ruta | Qué hace |
|---|---|---|
| POST | /subscriptions | Crea la suscripción y devuelve el checkout_url para el primer pago. |
| GET | /subscriptions | Lista (100 más recientes). Filtros: customer_id, customer_external_id, external_id, status. |
| GET | /subscriptions/{id} | Estado, próximo cobro, importe, tarjeta y últimos cobros. |
| POST | /subscriptions/{id}/update | Cambia name, lines, success_url o cancel_url. Las líneas nuevas se cobran desde el próximo periodo, sin prorrateo. Si hay un enlace de pago pendiente, sigue valiendo y ya cobra el importe nuevo. |
| POST | /subscriptions/{id}/cancel | Baja. {"at_period_end": true} (por defecto) la hace efectiva al vencer el periodo pagado; false, al momento. |
| POST | /subscriptions/{id}/checkout-link | Enlace para pagar cuando hay un pago pendiente (alta, impago o suspensión). {"send_email": true} se lo enviamos también al cliente. |
| POST | /subscriptions/{id}/card-update-link | Enlace para que el cliente cambie su tarjeta sin cobrar nada (suscripción al corriente). |
| POST | /subscriptions/{id}/items | Suma un cargo puntual al próximo cobro. |
| GET | /subscriptions/{id}/items | Cargos puntuales (100 más recientes). Filtro: status (pending o invoiced). |
| POST | /subscriptions/{id}/items/{item_id}/delete | Quita un cargo que aún no se ha cobrado. |
| GET | /subscriptions/{id}/invoices | Facturas emitidas de la suscripción. |
Crear una suscripción
Campos: customer_id o customer (objeto con los campos del cliente), name*, frequency (weekly, monthly, quarterly, yearly; por defecto monthly), frequency_value (1 a 12), lines* (description, quantity, unit_price sin IVA, tax_rate 21/10/4/0, discount_percent), first_charge_date, external_id, success_url, cancel_url, notes (aparece en la factura), send_email (por defecto false: tú rediriges al usuario).
first_charge_date (AAAA-MM-DD, hasta dentro de un año): día del primer cobro; los siguientes caen ese mismo día de cada periodo. Por defecto es hoy y el cliente paga el primer periodo al dar la tarjeta. Si es posterior, el cliente solo guarda la tarjeta (no se le cobra nada) y el primer cobro se hace solo ese día. Sirve para cobrar a todos el mismo día del mes, o para pasar a tarjeta a clientes que ya pagaban de otra forma sin cobrarles en el momento. Si el cliente no ha guardado la tarjeta cuando llega la fecha, el enlace pasa a pedirle el pago.
curl -X POST https://facturas.crescere.es/api/v1/subscriptions \
-H "Authorization: Bearer $CRESCERE_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: alta-suscripcion-4521" \
-d '{
"customer": {"fiscal_name":"Ejemplo SL","nif":"B12345678","email":"facturas@ejemplo.es","external_id":"acc_4521"},
"name": "Plan Pro",
"frequency": "monthly",
"lines": [{"description":"Plan Pro mensual","quantity":1,"unit_price":49.00,"tax_rate":21}],
"external_id": "sub_4521",
"success_url": "https://app.ejemplo.es/billing/ok",
"cancel_url": "https://app.ejemplo.es/billing/ko"
}'
Respuesta 201 (resumida):
{
"id": 12, "external_id": "sub_4521", "status": "pending_card", "name": "Plan Pro",
"customer": {"id": 31, "external_id": "acc_4521", "fiscal_name": "Ejemplo SL", ...},
"frequency": "monthly", "frequency_value": 1, "amount": 59.29, "currency": "EUR",
"next_charge_date": "2026-10-21", "next_charge_amount": 59.29, "pending_items": [],
"cancel_at_period_end": false, "card": null,
"checkout_url": "https://facturas.crescere.es/subscribe/3f9c...", "recent_charges": []
}
amount es lo que se cobra cada periodo; next_charge_amount, lo que se cobrará en el próximo, con los cargos puntuales pendientes (pending_items).
Al volver de Redsys, el usuario llega a tu success_url?subscription_id=12&status=succeeded (o status=processing si la confirmación del banco tarda unos segundos). Si el pago no sale, a tu cancel_url?subscription_id=12&status=failed&retry_url=.... No actives la cuenta solo por la redirección: hazlo con el aviso subscription.activated o consultando la suscripción.
Cargos puntuales
Para cobrar algo una sola vez junto con el próximo periodo: consumos del mes, días sueltos, una matrícula, un exceso de uso... El cargo va en el mismo cobro y en la misma factura que el periodo, así que el cliente ve un solo cargo en su tarjeta.
Campos: description*, unit_price* (sin IVA, mayor que cero), quantity (por defecto 1), tax_rate (por defecto 21), discount_percent, external_id (tu referencia del cargo; recomendado).
curl -X POST https://facturas.crescere.es/api/v1/subscriptions/12/items \
-H "Authorization: Bearer $CRESCERE_KEY" -H "Content-Type: application/json" \
-d '{"description":"Comedor, días sueltos de octubre (3)","quantity":3,"unit_price":6.50,"tax_rate":10,"external_id":"DS-4521-2610"}'
Respuesta 201:
{
"id": 7, "external_id": "DS-4521-2610", "description": "Comedor, días sueltos de octubre (3)",
"quantity": 3, "unit_price": 6.5, "discount_percent": 0, "tax_rate": 10, "total": 21.45,
"status": "pending", "invoice_id": null, "created_at": "2026-10-31T18:02:11+01:00"
}
- Si repites el cargo con el mismo
external_idy los mismos datos, recibes el que ya existe (200) y no se duplica. Con otros datos,409 external_id_exists: si aún no se ha cobrado, quítalo y vuelve a añadirlo. - Se cobra en el próximo cobro de la suscripción, sea el automático o el que haga el cliente desde un enlace. Al cobrarse pasa a
invoicedcon suinvoice_id, y el avisoinvoice.paidlo incluye enitems. - Para cobrar en un día concreto, añade los cargos antes de esa fecha (
next_charge_date): lo que llegue después va al cobro siguiente. - No se puede quitar un cargo ya cobrado (
409 item_invoiced) ni uno que va en un cobro que se está procesando (409 item_in_progress). - Hasta 100 cargos pendientes por suscripción.
6. Facturas
| Método | Ruta | Qué hace |
|---|---|---|
| GET | /invoices/{id}/pdf | PDF de la factura, con el QR de Verifactu. Requiere la clave de API: descárgalo desde tu servidor. |
Cada factura incluye id, number, date, subtotal, total_tax, total, payment_status, amount_paid, subscription_id, pdf_url y verifactu (huella y URL de verificación de la AEAT).
7. Estados de una suscripción
| status | Significado | Qué hacer en tu plataforma |
|---|---|---|
pending_card | Creada; el cliente aún no ha dado su tarjeta. | Cuenta sin activar. |
active | Al corriente; se cobra sola cada periodo. | Cuenta activa. |
past_due | Ha fallado un cobro; estamos reintentando y el cliente tiene un enlace para pagar. | Normalmente, cuenta activa con un aviso. |
suspended | 10 días sin poder cobrar. No se cobra hasta que el cliente pague desde el enlace. | Bloquear la cuenta. |
cancelled | Dada de baja; no se cobra más y la tarjeta se borra. | Cerrar la cuenta. |
paused / finished | Pausada desde nuestra app, o finalizada por fecha o número de cobros. | Según tu caso. |
8. Avisos (webhooks)
Configura la URL (https) en Mi empresa > API y avisos. Te enviamos un POST JSON por cada evento:
| Evento | Cuándo |
|---|---|
subscription.activated | El cliente ha guardado su tarjeta: ha pagado el primer periodo o, con first_charge_date posterior, se le cobrará ese día. |
invoice.paid | Cada cobro correcto. Incluye invoice e items (los cargos puntuales cobrados con esa factura). |
subscription.payment_failed | Un cobro automático ha fallado. Incluye failure (motivo, intento, next_retry_date, suspends_on, sca_required) y el checkout_url que ha recibido el cliente. |
subscription.suspended | 10 días sin cobrar: la suscripción se suspende. |
subscription.reactivated | El cliente ha pagado tras un impago o una suspensión. |
subscription.card_updated | El cliente ha guardado otra tarjeta. |
subscription.cancelled | La baja se ha hecho efectiva. |
test | Aviso de prueba enviado desde Ajustes. |
{
"id": "6f1c0e3a-2b6d-4b8e-9c1a-2f5e7d9a0b11",
"type": "invoice.paid",
"created_at": "2026-11-21T07:00:12+01:00",
"livemode": true,
"data": {
"subscription": { "id": 12, "external_id": "sub_4521", "status": "active", ... },
"invoice": { "id": 87, "number": "F2026-0042", "total": 59.29, "pdf_url": "https://facturas.crescere.es/api/v1/invoices/87/pdf", ... },
"items": []
}
}
- Responde con un código
2xxen menos de 5 segundos; procesa después si necesitas más tiempo. - Si no respondes con
2xx, reintentamos a los 1, 5 y 30 minutos, y a las 2, 6, 12 y 24 horas. Si sigue fallando, avisamos por email a la empresa. - Un aviso puede llegar más de una vez: usa
id(también en la cabeceraX-Crescere-Delivery) para no procesarlo dos veces. El tipo va también enX-Crescere-Event.
9. Verificar la firma
Cada aviso lleva X-Crescere-Signature: t=<timestamp>,v1=<firma>. La firma es el HMAC-SHA256 en hexadecimal, con tu secreto de avisos, de <timestamp>.<cuerpo exacto recibido>. Rechaza el aviso si la firma no coincide o si el timestamp tiene más de 5 minutos.
PHP
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_CRESCERE_SIGNATURE'] ?? ''), $sig);
$expected = hash_hmac('sha256', ($sig['t'] ?? '') . '.' . $body, getenv('CRESCERE_WEBHOOK_SECRET'));
if (!hash_equals($expected, $sig['v1'] ?? '') || abs(time() - (int) ($sig['t'] ?? 0)) > 300) {
http_response_code(400);
exit;
}
$event = json_decode($body, true);
http_response_code(200);
Node.js (Express)
const crypto = require('crypto');
app.post('/webhooks/crescere', express.raw({ type: 'application/json' }), (req, res) => {
const sig = Object.fromEntries(req.get('X-Crescere-Signature').split(',').map(p => p.split('=')));
const expected = crypto.createHmac('sha256', process.env.CRESCERE_WEBHOOK_SECRET)
.update(`${sig.t}.${req.body}`).digest('hex');
const valid = sig.v1 && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig.v1))
&& Math.abs(Date.now() / 1000 - Number(sig.t)) <= 300;
if (!valid) return res.sendStatus(400);
const event = JSON.parse(req.body);
res.sendStatus(200);
});
10. Probar la integración
- Con el TPV de la empresa en modo pruebas, paga con la tarjeta de pruebas de Redsys
4548 8100 0000 0003, caducidad12/49, CVV123. No se cobra dinero real. - En Mi empresa > API y avisos tienes un receptor de pruebas: usa su URL como destino de los avisos y verás cada aviso recibido, con la firma comprobada. Puedes ponerlo a responder con error para ver los reintentos.
- El botón Enviar aviso de prueba manda un evento
testa tu URL.