Crescere.VF

API de suscripciones

Cobro automático con tarjeta (Redsys), facturas con Verifactu y avisos a tu plataforma.

1. Cómo funciona

  1. Tu backend crea la suscripción con POST /subscriptions, indicando el cliente, el importe y tu success_url. Recibes un checkout_url.
  2. 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 un first_charge_date posterior a hoy, solo guarda la tarjeta y no paga nada hasta ese día.
  3. Redsys devuelve al usuario a tu success_url y te llega el aviso subscription.activated. La factura del periodo se emite con Verifactu, se le envía por email y te llega invoice.paid.
  4. 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.
  5. 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

4. Clientes

MétodoRutaQué hace
POST/customersCrea 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étodoRutaQué hace
POST/subscriptionsCrea la suscripción y devuelve el checkout_url para el primer pago.
GET/subscriptionsLista (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}/updateCambia 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}/cancelBaja. {"at_period_end": true} (por defecto) la hace efectiva al vencer el periodo pagado; false, al momento.
POST/subscriptions/{id}/checkout-linkEnlace 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-linkEnlace para que el cliente cambie su tarjeta sin cobrar nada (suscripción al corriente).
POST/subscriptions/{id}/itemsSuma un cargo puntual al próximo cobro.
GET/subscriptions/{id}/itemsCargos puntuales (100 más recientes). Filtro: status (pending o invoiced).
POST/subscriptions/{id}/items/{item_id}/deleteQuita un cargo que aún no se ha cobrado.
GET/subscriptions/{id}/invoicesFacturas 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"
}

6. Facturas

MétodoRutaQué hace
GET/invoices/{id}/pdfPDF 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

statusSignificadoQué hacer en tu plataforma
pending_cardCreada; el cliente aún no ha dado su tarjeta.Cuenta sin activar.
activeAl corriente; se cobra sola cada periodo.Cuenta activa.
past_dueHa fallado un cobro; estamos reintentando y el cliente tiene un enlace para pagar.Normalmente, cuenta activa con un aviso.
suspended10 días sin poder cobrar. No se cobra hasta que el cliente pague desde el enlace.Bloquear la cuenta.
cancelledDada de baja; no se cobra más y la tarjeta se borra.Cerrar la cuenta.
paused / finishedPausada 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:

EventoCuándo
subscription.activatedEl cliente ha guardado su tarjeta: ha pagado el primer periodo o, con first_charge_date posterior, se le cobrará ese día.
invoice.paidCada cobro correcto. Incluye invoice e items (los cargos puntuales cobrados con esa factura).
subscription.payment_failedUn 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.suspended10 días sin cobrar: la suscripción se suspende.
subscription.reactivatedEl cliente ha pagado tras un impago o una suspensión.
subscription.card_updatedEl cliente ha guardado otra tarjeta.
subscription.cancelledLa baja se ha hecho efectiva.
testAviso 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": []
  }
}

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