Desarrolladores

Race Dept. en tu sitio.

Un botón, la inscripción dentro de tu página o un link; una API para leer tus eventos, resultados y órdenes, y webhooks para que tu sistema se entere de cada venta. Gratis para todos los organizadores.

Empieza en dos minutos

Tu sitio conserva su diseño; la inscripción, el carrito, el deslinde y el pago corren en Race Dept. Tu página nunca toca datos de tarjetas.

  1. En tu panel, entra a Configuración › Desarrolladores y agrega tu sitio en Sitios autorizados (por ejemplo https://tusitio.com).
  2. Ahí mismo elige el evento y copia el código.
  3. Pégalo en tu página. En tu computadora (localhost) funciona sin autorizarlo, para que pruebes.
<script src="https://racedept.mx/v1/racedept.js" defer></script>
<button data-racedept-evento="tu-organizacion/tu-evento">Inscribirme</button>

Botón

Cualquier botón o link con data-racedept-evento abre la inscripción encima de tu página; en celular, a pantalla completa. Los estilos del botón son los tuyos.

AtributoQué hace
data-racedept-eventoEl evento, como organizacion/evento (lo ves en la dirección de su página en Race Dept.).
data-racedept-colorOpcional. El color de acento en hexadecimal, por ejemplo #2151FF.
data-racedept-tiendaEn lugar de un evento, tu tienda: data-racedept-tienda="organizacion" data-racedept-modo="ventana".

Dentro de tu página

La inscripción vive en una sección de tu página y crece con su contenido: categorías, carrito, datos de cada participante, deslinde, pago y confirmación con folio.

<script src="https://racedept.mx/v1/racedept.js" defer></script>
<div data-racedept-inscripcion="tu-organizacion/tu-evento"></div>

<!-- o tu tienda -->
<div data-racedept-tienda="tu-organizacion"></div>

Funciona también en sitios que pintan con JavaScript (React, Vue, Webflow, Wix, Shopify): lo que se agregue después se monta solo.

Eventos en tu página

El script avisa en el elemento (y en document) lo que pasa adentro:

Eventodetail
racedept:completado{ folio, estado, evento, tienda }. estado es pagada, o pendiente_pago si eligió OXXO o transferencia.
racedept:abiertoSe abrió la ventana. Si tu sitio usa scroll suave (Lenis, Locomotive), páralo aquí y reanúdalo al cerrar.
racedept:cerradoCerró la ventana.
racedept:paso{ ruta }: avanzó de paso (carrito, datos, pago…).
racedept:sin-cookiesSu navegador no permite comprar dentro de tu página; se le ofrece seguir en una pestaña nueva.
<script>
  document.addEventListener("racedept:completado", (e) => {
    // Confírmalo en tu servidor antes de darlo por pagado (ver abajo)
    fetch("/mi-api/confirmar?folio=" + e.detail.folio);
  });

  // También desde JavaScript:
  RaceDept.abrir({ evento: "tu-organizacion/tu-evento", color: "#2151FF", alCompletar: (d) => RaceDept.cerrar() });
  RaceDept.montar("#inscripcion", { evento: "tu-organizacion/tu-evento" });
</script>

Confirma la orden en tu servidor

Lo que llega al navegador se puede alterar. Antes de dar acceso a algo, consulta la orden desde tu servidor con tu llave secreta; o mejor, usa los webhooks.

curl https://racedept.mx/api/v1/ordenes/ORD-7K2M9Q4P \
  -H "Authorization: Bearer rd_sk_live_…"
{
  "folio": "ORD-7K2M9Q4P",
  "tipo": "inscripcion",
  "estado": "pagada",
  "metodo": "tarjeta",
  "moneda": "MXN",
  "subtotal": 120000,
  "descuento": 0,
  "cupon": null,
  "cargoServicio": 8000,
  "envio": 0,
  "total": 128000,
  "comprador": { "nombre": "Ana López", "email": "ana@correo.com" },
  "evento": { "slug": "fecha-1-2027", "nombre": "Fecha 1", "fecha": "2027-02-20" },
  "inscripciones": [
    { "folio": "ABC-4H8N2C6W", "nombre": "Ana López", "email": "ana@correo.com",
      "categoria": "Elite Femenil", "dorsal": 214, "estado": "confirmada" }
  ],
  "productos": [],
  "creada": "2027-02-10T18:20:41.000Z",
  "pagada": "2027-02-10T18:22:05.000Z"
}

Los montos van en centavos de peso mexicano, como en Stripe.

API

Base: https://racedept.mx/api/v1. Manda tu llave en la cabecera Authorization: Bearer <llave>. Las respuestas son JSON; los errores, { error: { codigo, mensaje } }. Hasta 600 llamadas por minuto por llave.

LlavePara qué
rd_pk_…Publicable. Puede ir en tu página: lee lo que ya es público y solo responde a tus sitios autorizados.
rd_sk_…Secreta. Solo desde tu servidor; también lee órdenes e inscripciones. Si se filtra, revócala en el panel.
EndpointDevuelve
GET /eventosTus eventos públicos: fechas, sede, cartel, precio desde, ventana de venta y sus direcciones.
GET /eventos/{slug}El evento con sus categorías (precio vigente y si está agotada), productos y serial.
GET /eventos/{slug}/resultadosLos resultados, si ya están publicados.
GET /seriales/{slug}/rankingLa clasificación del serial, general y por categoría.
GET /ordenes/{folio}Solo llave secreta. La orden: estado, montos, comprador, inscripciones y productos.
GET /inscripciones?evento={slug}Solo llave secreta. Las inscripciones pagadas, de 100 en 100 (pagina, estado).

Webhooks

Agrega la dirección de tu servidor en Configuración › Desarrolladores › Webhooks. Race Dept. le manda un POST con JSON cada vez que:

TipoCuándo
orden.pagadaUna orden quedó pagada (inscripciones o un pedido de la tienda). Trae la orden completa.
orden.canceladaSe canceló, o venció sin pagarse en OXXO o transferencia (motivo).
orden.reembolsadaSe reembolsó en línea, completa o en parte (completo).
resultados.publicadosPublicaste los resultados de un evento.
POST https://tusitio.com/webhooks/racedept
RaceDept-Evento: orden.pagada
RaceDept-Entrega: 3c1f…
RaceDept-Firma: t=1802367725,v1=5f2b…

{ "id": "evt_…", "tipo": "orden.pagada", "creado": "2027-02-10T18:22:05.000Z", "datos": { "orden": { … } } }

Responde con un código 2xx en menos de 10 segundos. Si no, se reintenta a 1 y 5 minutos, media hora, 2, 6 y 24 horas; puedes reenviar desde el panel. Un mismo aviso puede llegar más de una vez: usa id para no procesarlo dos veces.

Comprueba la firma con el secreto del webhook:

import crypto from "node:crypto";

// cuerpo: el texto tal cual llegó (antes de convertirlo a JSON)
export function firmaValida(cuerpo, cabecera, secreto) {
  const p = Object.fromEntries(String(cabecera).split(",").map((x) => x.split("=")));
  const esperada = crypto.createHmac("sha256", secreto).update(`${p.t}.${cuerpo}`).digest("hex");
  const reciente = Math.abs(Date.now() / 1000 - Number(p.t)) < 300;
  return reciente && typeof p.v1 === "string" && p.v1.length === esperada.length &&
    crypto.timingSafeEqual(Buffer.from(p.v1), Buffer.from(esperada));
}

// Express
app.post("/webhooks/racedept", express.raw({ type: "application/json" }), (req, res) => {
  const cuerpo = req.body.toString();
  if (!firmaValida(cuerpo, req.get("RaceDept-Firma"), process.env.RACEDEPT_SECRETO)) return res.sendStatus(400);
  const aviso = JSON.parse(cuerpo);
  if (aviso.tipo === "orden.pagada") {
    // aviso.datos.orden.folio, aviso.datos.orden.inscripciones…
  }
  res.sendStatus(200);
});

Pruebas

En tu computadora todo funciona en localhost sin autorizarlo. Para probar el pago de punta a punta sin cobrar, escríbenos y te damos acceso al ambiente de prueba: carga el script desde su dirección y paga con la tarjeta 4242 4242 4242 4242, cualquier fecha futura y cualquier CVC.

<script src="https://racedept-prueba.vercel.app/v1/racedept.js" defer></script>