Aviso de seguridad: Waycard opera exclusivamente en waycard.app. Reportar fraude
Idioma
Cómo funciona › Endpoints › waycard.app › Solicitar acceso
Para empresas · API

API de cupones
de tarjeta Waycard

Vende tarjetas Waycard desde tu plataforma: cobras a tu cliente, pides el cupón por API y se lo entregas. Él lo canjea en la app y recibe su tarjeta.

URL base https://api.waycard.info/v2/cupon
POST /v2/cupon/emitir
X-Cupon-Key:  wck_9f2a…
X-Cupon-Ts:   1758456000
X-Cupon-Sign: 4e1d…c07a

{ "origen_ref": "order-12345" }
// 200 OK
{
  "ok": true,
  "codigo_visible": "WAY-9WZJ-A3K7-M2P5-R8T",
  "caduca": "2027-09-28",
  "cupo_restante": 41,
  "marca": "acme"
}

1Cómo funciona

1
Alta y credenciales
Waycard da de alta tu empresa y te entrega una clave y un secreto.
2
Prepago
Compras los cupones que quieras, de uno a miles. Cuando recibimos el pago, se suman a tu saldo.
3
Emites al vender
En cada venta llamas a /emitir y recibes un código. Se descuenta uno de tu saldo.
4
Tu cliente canjea
Introduce el código en la app de Waycard y obtiene su tarjeta sin coste.
  • Con el saldo a 0, /emitir responde 402 SIN_SALDO y no se crea nada.
  • Si anulas un cupón antes de que se canjee, vuelve a tu saldo. Uno ya canjeado no se puede anular.
  • El cupón es al portador: quien tenga el código puede canjearlo. Entrégalo por un canal seguro.

Tu marca en la app

Si lo acordáis con Waycard, tus cupones pueden llevar tu marca: los colores y las imágenes de tu empresa. Cuando alguien canjea uno de tus cupones, la app cambia a tu diseño al momento y la marca queda guardada en su cuenta.

  • No tienes que enviar nada: la marca va asociada a tus credenciales y la asigna Waycard.
  • Cada cupón guarda la marca que tenías al emitirlo. Si cambia, los ya vendidos siguen con la anterior.
  • Las respuestas incluyen el campo marca con el identificador de tu marca, o null.

2Credenciales

DatoEjemploPara qué
X-Cupon-Keywck_9f2a…Identifica tu plataforma. Va en la cabecera de cada petición.
Secretowcs_4b1c…Firma las peticiones. Nunca viaja en ninguna petición ni se comparte.

El secreto se muestra una sola vez. Si lo pierdes, pide una rotación: las credenciales anteriores dejan de funcionar al instante.

Opcionalmente puedes darnos las IPs desde las que llamarás (lista blanca) y una URL de webhook.

3Firma de las peticiones

Todas las peticiones llevan tres cabeceras:

X-Cupon-Key:  <key>
X-Cupon-Ts:   <unix epoch, seconds, UTC>
X-Cupon-Sign: HMAC_SHA256(secret, rawBody + X-Cupon-Ts)   // hex
  • rawBody es exactamente el texto JSON que envías (los mismos bytes). Serializa una vez, firma ese texto y envía ese mismo texto.
  • En GET no hay cuerpo: se firma solo el timestamp.
  • Se admiten ±5 minutos de desvío. Si recibes «X-Cupon-Ts fuera de ventana», revisa el reloj de tu servidor (NTP).
  • Content-Type: application/json en las peticiones POST.

Ejemplos

import crypto from 'crypto';

const BASE = 'https://api.waycard.info/v2/cupon';
const KEY = process.env.WAYCARD_CUPON_KEY;
const SECRET = process.env.WAYCARD_CUPON_SECRET;

async function call(method, path, body) {
  const raw = body ? JSON.stringify(body) : '';
  const ts = String(Math.floor(Date.now() / 1000));
  const sign = crypto.createHmac('sha256', SECRET).update(raw + ts).digest('hex');
  const res = await fetch(BASE + path, {
    method,
    headers: {
      'Content-Type': 'application/json',
      'X-Cupon-Key': KEY, 'X-Cupon-Ts': ts, 'X-Cupon-Sign': sign,
    },
    body: body ? raw : undefined,
  });
  return { status: res.status, ...(await res.json()) };
}

// Issue a coupon after charging order 12345
const r = await call('POST', '/emitir', { origen_ref: 'order-12345' });
if (r.ok) console.log(r.codigo_visible, r.caduca, r.cupo_restante);
else if (r.status === 402) console.log('No balance left: top up with Waycard');
function waycardCoupon(string $method, string $path, ?array $body = null): array {
    $raw  = $body === null ? '' : json_encode($body, JSON_UNESCAPED_UNICODE);
    $ts   = (string)time();
    $sign = hash_hmac('sha256', $raw . $ts, getenv('WAYCARD_CUPON_SECRET'));
    $ch = curl_init('https://api.waycard.info/v2/cupon' . $path);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 15,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'X-Cupon-Key: '  . getenv('WAYCARD_CUPON_KEY'),
            'X-Cupon-Ts: '   . $ts,
            'X-Cupon-Sign: ' . $sign,
        ],
        CURLOPT_POSTFIELDS     => $body === null ? null : $raw,
    ]);
    $res  = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    return ['status' => $code] + (json_decode($res, true) ?: []);
}

$r = waycardCoupon('POST', '/emitir', ['origen_ref' => 'order-12345']);
import hmac, hashlib, json, os, time, requests

BASE = 'https://api.waycard.info/v2/cupon'
KEY, SECRET = os.environ['WAYCARD_CUPON_KEY'], os.environ['WAYCARD_CUPON_SECRET']

def call(method, path, body=None, params=None):
    raw = json.dumps(body, separators=(',', ':')) if body is not None else ''
    ts = str(int(time.time()))
    sign = hmac.new(SECRET.encode(), (raw + ts).encode(), hashlib.sha256).hexdigest()
    r = requests.request(method, BASE + path, params=params, data=raw or None, timeout=15,
                         headers={'Content-Type': 'application/json',
                                  'X-Cupon-Key': KEY, 'X-Cupon-Ts': ts, 'X-Cupon-Sign': sign})
    return r.status_code, r.json()

status, r = call('POST', '/emitir', {'origen_ref': 'order-12345'})

4Endpoints

POST/emitir
Crear un cupón

Llámalo después de haber cobrado a tu cliente.

CampoTipoObligatorioDescripción
origen_refstring ≤ 80SíTu identificador del cobro o pedido. Es la clave de idempotencia: repetir la llamada con el mismo valor devuelve el mismo cupón y no descuenta otro.
productostring ≤ 20NoPor defecto CARD (tarjeta).
caduca_diasint 0-3650NoDías de validez. 0 = sin caducidad. Por defecto, el acordado con Waycard.
notastring ≤ 190NoTexto libre para tu referencia (cliente, campaña…).
Respuesta · 200
{
  "ok": true,
  "codigo": "WAY9WZJA3K7M2P5R8T",
  "codigo_visible": "WAY-9WZJ-A3K7-M2P5-R8T",
  "caduca": "2027-09-28",
  "estado": "EMITIDO",
  "reemitido": false,
  "cupo_restante": 41,
  "marca": "acme"
}
  • codigo es lo que se guarda (18 caracteres, sin guiones); codigo_visible es el mismo con guiones, para enseñárselo al cliente. La app acepta los dos.
  • reemitido: true significa que ese origen_ref ya tenía cupón y se te devuelve el existente.
  • cupo_restante: los cupones que te quedan. marca: la marca que lleva el cupón, o null.
Errores
HTTPerrorQué hacer
400FALTA_ORIGEN_REFFalta origen_ref.
400ORIGEN_AJENOHas enviado un campo origen que no es el tuyo. No hace falta enviarlo.
400CAMPO_LARGOAlgún campo es demasiado largo.
401—Revisa las credenciales, la firma sobre el cuerpo crudo y el reloj.
402SIN_SALDONo quedan cupones prepagados. No se ha creado nada: contacta con Waycard para recargar.
429—Más de 120 peticiones por minuto, o demasiadas rechazadas desde tu IP. Espera un minuto.
POST/anular
Anular un cupón sin canjear

Para cuando devuelves el dinero a tu cliente.

CampoTipoObligatorioDescripción
origen_refstringSíTu identificador del cobro o pedido.
motivostring ≤ 160NoMotivo de la anulación, para tu referencia.
Respuesta · 200
{ "ok": true, "anulado": true, "cupo_restante": 42 }
  • Si no se puede anular: anulado: false y motivo NO_EXISTE, YA_CANJEADO o YA_ANULADO. Con YA_CANJEADO no se devuelve saldo: la tarjeta puede estar emitida.
GET/saldo
Tu saldo y tus cupones

Cuántos cupones te quedan y el resumen de los emitidos.

{
  "ok": true,
  "origen": "acme",
  "prepago": true,
  "cupo_restante": 42,
  "caduca_dias_defecto": 365,
  "marca": "acme",
  "cupones": { "total": 58, "sin_usar": 20, "canjeados": 35, "anulados": 3, "caducados": 0 }
}
GET/estado?origen_ref=…
Estado de un cupón tuyo

Para saber si se ha canjeado sin depender del webhook.

{
  "ok": true, "existe": true, "origen_ref": "order-12345",
  "codigo": "WAY9WZJA3K7M2P5R8T", "codigo_visible": "WAY-9WZJ-A3K7-M2P5-R8T",
  "estado": "CANJEADO",
  "caduca": "2027-09-28",
  "canjeado_el": "2026-10-02 18:41:07",
  "creado": "2026-09-28 12:00:00",
  "marca": "acme"
}
  • estado: EMITIDO, CANJEADO, ANULADO o CADUCADO. Si el origen_ref no es tuyo o no existe: existe: false.

5Webhook de canje

Si nos das una URL https, te avisamos cuando se canjee uno de tus cupones, firmado con tus credenciales:

POST <your webhook>
X-Cupon-Key:  <your key>
X-Cupon-Ts:   <epoch>
X-Cupon-Sign: HMAC_SHA256(your secret, body + ts)

{ "codigo": "WAY9WZJA3K7M2P5R8T", "canjeado_el": "2026-10-02 18:41:07" }

Verifica la firma igual que nosotros verificamos la tuya. Se envía una vez, sin reintentos: si necesitas certeza, consulta /estado.

6Buenas prácticas

  • Idempotencia: usa siempre el mismo origen_ref para el mismo cobro. Ante un timeout, repite la llamada tal cual.
  • Secreto: en variables de entorno o en un gestor de secretos, nunca en el código ni en el front.
  • Saldo: consulta /saldo antes de una campaña grande y avisa a Waycard con antelación para recargar.
  • Reloj: mantén NTP en tu servidor; la firma caduca a los 5 minutos.
  • Códigos: no los envíes por canales abiertos ni los guardes en logs. Son al portador.

¿Quieres vender tarjetas Waycard?

Escríbenos y te damos de alta, con tus credenciales y, si quieres, tu marca en la app.

Solicitar acceso
Chat