API Tienda Capta
Su app vende con su propio catálogo (productos, precios, imágenes) y cobra a crédito con vales Capta. Ustedes crean el pedido en esta API, el comprador firma sus vales en la app de Capta, ustedes surten y nos reportan el envío. A partir de ahí Capta cobra al comprador. No requiere sesión de usuario: solo una API Key por app.
X-Api-Key: <su llave>
https://marketplace.capta.best/api/tienda-externa
{baseUrl}/api/tienda-externa
/plazos | Plazos (pagos quincenales) disponibles |
/disponible | Quién es el comprador y cuánto puede financiar |
/cotizar | Montos por vale y si cabe en el crédito (no guarda nada) |
/pedidos | Crear el pedido (idempotente por externoId) |
/pedidos | Sus pedidos con filtros y páginas |
/pedidos/{externoId} | Estado completo de un pedido |
/pedidos/{externoId}/enviado | Reportar el envío (con notasEnvio) |
/pedidos/{externoId}/cancelar | Cancelar mientras no haya vales firmados |
Flujo del pedido
Tipo cliente (el más común). Cada cambio les llega como webhook firmado.
| # | Quién | Qué pasa | Webhook |
|---|---|---|---|
| 1 | Su app | GET /disponible y POST /cotizar: muestran al comprador cuánto pagará. | |
| 2 | Su app | POST /pedidos: el pedido nace pendiente. Capta avisa al Enlace. | |
| 3 | Enlace | Lo autoriza en su app eligiendo el plan (intereses y pago pueden bajar). | pedido.autorizado |
| 4 | Comprador | Firma cada vale en la app de Capta. | vale.firmado pedido.firmado |
| 5 | Su app | Surte y reporta POST /pedidos/{id}/enviado. Empieza el calendario de pagos. | pedido.enviado |
| 6 | Comprador | Termina de pagar sus vales. | pedido.pagado |
Tipo enlace: igual, pero el paso 3 y 4 son uno solo: la Enlace firma todos sus vales a la vez y con eso queda autorizado (llegan juntos pedido.autorizado, un vale.firmado por vale y pedido.firmado).
No surtan el pedido hasta recibir pedido.firmado (o ver firmado: true en GET /pedidos/{externoId}). Antes de eso el envío se rechaza.
Cancelación: ustedes pueden cancelar mientras ningún vale esté firmado. El Enlace también puede rechazar el pedido; en los dos casos llega pedido.cancelado.
Conceptos
tipo | Quién debe | Quién autoriza | Dónde firma | Intereses |
|---|---|---|---|---|
cliente default | El cliente, con su crédito y la línea de su Enlace | Su Enlace, eligiendo el plan | App Capta de clientes | Según el plan |
enlace | La Enlace, con su crédito personal | Ella misma, al firmar | App Capta de Enlaces | Sin intereses (total ÷ pagos) |
- Comprador: se identifica por
tipo+celular(10 dígitos; se aceptan espacios, guiones y+52). Debe estar activo en Capta y su Enlace habilitado para recibir pedidos. Si el celular no es de nadie en Capta no se puede vender a crédito: la persona debe darse de alta con un Enlace. - Plazo: número de pagos quincenales; cada producto lleva el suyo (uno de
GET /plazos). Los productos con el mismo plazo forman un vale; dos plazos = dos vales, y el comprador firma cada uno. - Flete: lo mandan ustedes para todo el pedido. Se reparte en partes iguales entre los vales (el último absorbe los centavos) y se financia con los productos.
- Precios: unitarios, con IVA, en pesos MXN. Se usan tal como los mandan.
- Crédito: tipo
cliente, el capital (productos + flete) debe caber en el crédito del cliente y en la línea de su Enlace; tipoenlace, el total en su crédito personal. Si no cabe, el pedido se rechaza. - Plan (tipo
cliente): la cotización usa el plan inicial (Tu Decides 20 %). Al autorizar, el Enlace elige el definitivo y los montos pueden bajar; los finales llegan enpedido.autorizado. externoId: su número de pedido, único por app. Si reintentanPOST /pedidoscon el mismo, se regresa el pedido ya creado: no se duplica.
Autenticación
Cada app tiene una llave. Mándenla en el header X-Api-Key de cada petición. Sin llave o con una llave inválida la respuesta es 401. Todas las peticiones y respuestas son JSON; las exitosas vienen en { "data": ... }.
La llave se entrega una sola vez por un canal seguro y Capta solo guarda su hash. No la pongan en el código del frontend ni en repositorios; si se pierde o se filtra, pidan una nueva y la anterior se desactiva.
Sandbox
Para desarrollar en cualquier momento, sin depender de que el servidor de pruebas de Capta esté encendido. Los ejemplos de esta guía apuntan al sandbox: https://marketplace.capta.best.
- Cada llamada se manda primero a la API real de pruebas de Capta. Si no responde, contesta un simulador con las mismas reglas, mensajes y formatos; la cotización usa la misma fórmula (mismos montos al centavo).
- El header
X-Sandbox-Origendice quién contestó:tunelosimulado.GET https://marketplace.capta.best/sandboxmuestra el estado de la conexión con Capta y los compradores de prueba. - Los pedidos creados en el simulador solo existen en el simulador, y los que contesta Capta solo en Capta: si la conexión cambia, un pedido del otro lado da
404. - Usen su llave real en todas las llamadas, también en las acciones de
/sandbox: el sandbox y Capta la validan, y sin ella la respuesta es401.
Errores
| Código | Cuándo | Cuerpo |
|---|---|---|
400 | Datos inválidos (campo faltante, formato, filtro) | { "message": "..." } |
401 | Sin X-Api-Key o llave inválida | vacío |
404 | Comprador o pedido no encontrado | { "statusCode": 404, "message": "..." } |
422 | Regla de negocio: Enlace no habilitado, crédito insuficiente, plazo no disponible, pedido no autorizado o sin firmar, ya no se puede cancelar… | { "statusCode": 422, "message": "..." } |
500 | Error de Capta; reintenten más tarde | { "StatusCode": 500, "Message": "..." } |
El message está en español y se puede mostrar al usuario.
Plazos disponibles
Números de pagos quincenales que se pueden elegir para cada producto.
curl "https://marketplace.capta.best/api/tienda-externa/plazos" \
-H "X-Api-Key: $CAPTA_API_KEY"
{ "data": [8, 10, 12, 14, 18] }
Comprador y crédito disponible
Quién es el comprador, si su Enlace puede recibir pedidos y cuánto puede financiar.
curl "https://marketplace.capta.best/api/tienda-externa/disponible?celular=6670000001&tipo=cliente" \
-H "X-Api-Key: $CAPTA_API_KEY"
| Parámetro | Tipo | Descripción |
|---|---|---|
celular | requerido | 10 dígitos (se aceptan espacios, guiones y +52). |
tipo | opcional | cliente (default) o enlace. |
{
"data": {
"tipo": "cliente",
"clienteId": 800001,
"nombre": "MARIA PRUEBA LOPEZ",
"enlaceId": 9001,
"enlaceNombre": "ANA ENLACE PRUEBA",
"puedeRecibirPedidos": true,
"disponible": 15000.00,
"disponibleCliente": 15000.00,
"disponibleEnlace": 50000.00
}
}
disponible: lo máximo que puede financiar (tipo cliente: capital = productos + flete; tipo enlace: total). puedeRecibirPedidos: false = el pedido se rechazará. En tipo enlace, clienteId y disponibleCliente vienen en null y disponibleEnlace es su crédito personal.
404 si no hay comprador activo con ese celular; 422 si hay más de uno, si el cliente no tiene Enlace o si tipo no es válido.
Cotizar
Cuánto pagará el comprador, vale por vale, y si cabe en su crédito. No guarda nada.
curl -X POST "https://marketplace.capta.best/api/tienda-externa/cotizar" \
-H "X-Api-Key: $CAPTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tipo": "cliente",
"celular": "667 000 0001",
"productos": [
{ "sku": "TV-55", "nombre": "Pantalla 55\"", "precio": 8999.00, "imagenUrl": "https://ejemplo.com/tv.jpg", "cantidad": 1, "plazo": 18 },
{ "sku": "SOP-01", "nombre": "Soporte de pared", "precio": 499.00, "imagenUrl": null, "cantidad": 1, "plazo": 12 }
],
"flete": 250.00,
"transportista": "ESTAFETA"
}'
| Campo | Tipo | Descripción |
|---|---|---|
tipo | opcional | cliente (default) o enlace. |
celular | requerido | 10 a 20 caracteres. |
productos | requerido | 1 a 100. |
productos[].sku | requerido | Máx. 50. |
productos[].nombre | requerido | Máx. 300. |
productos[].precio | requerido | 0.01 a 1,000,000. Unitario con IVA. |
productos[].imagenUrl | opcional | URL http(s), máx. 500. |
productos[].cantidad | requerido | 1 a 9999. |
productos[].plazo | requerido | Uno de GET /plazos. |
flete | opcional | 0 a 100,000. Del pedido completo. |
transportista | opcional | Máx. 20. |
{
"data": {
"subtotal": 9498.00,
"flete": 250.00,
"intereses": 8379.74,
"total": 18127.74,
"pagoQuincenal": 1036.58,
"disponibleCliente": 15000.00,
"disponibleEnlace": 50000.00,
"cabeEnCredito": true,
"vales": [
{ "plazo": 12, "subtotal": 499.00, "flete": 125.00, "capital": 624.00, "intereses": 437.40, "total": 1061.40, "pagoQuincenal": 88.45 },
{ "plazo": 18, "subtotal": 8999.00, "flete": 125.00, "capital": 9124.00, "intereses": 7942.34, "total": 17066.34, "pagoQuincenal": 948.13 }
]
}
}
pagoQuincenal del pedido es la suma de los vales mientras corren todos. En tipo enlace el mismo pedido da intereses: 0, total: 9748.00 y pagoQuincenal: 558.89 (52.00 + 506.89).
Crear pedido
Lo mismo que cotizar más el número de pedido, la entrega y notas. Nace pendiente y Capta avisa al Enlace para que lo autorice (tipo cliente) o lo firme (tipo enlace).
curl -X POST "https://marketplace.capta.best/api/tienda-externa/pedidos" \
-H "X-Api-Key: $CAPTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"externoId": "A-100234",
"tipo": "cliente",
"celular": "6670000001",
"productos": [
{ "sku": "TV-55", "nombre": "Pantalla 55\"", "precio": 8999.00, "imagenUrl": "https://ejemplo.com/tv.jpg", "cantidad": 1, "plazo": 18 },
{ "sku": "SOP-01", "nombre": "Soporte de pared", "precio": 499.00, "imagenUrl": null, "cantidad": 1, "plazo": 12 }
],
"flete": 250.00,
"transportista": "ESTAFETA",
"entrega": {
"calle": "Av. Obregón", "numeroExterior": "850", "numeroInterior": null, "colonia": "Centro",
"codigoPostal": "80000", "municipio": "Culiacán", "estado": "Sinaloa", "referencias": "Portón negro"
},
"notas": "Entregar por la tarde"
}'
| Campo | Tipo | Descripción |
|---|---|---|
externoId | requerido | Su número de pedido. Máx. 64, único por app. |
entrega.calle | requerido | Máx. 150. |
entrega.numeroExterior | requerido | Máx. 20. |
entrega.numeroInterior | opcional | Máx. 20. |
entrega.colonia, municipio, estado | requerido | Máx. 100. |
entrega.codigoPostal | requerido | 5 dígitos. |
entrega.referencias | opcional | Máx. 200. |
notas | opcional | Máx. 200. |
201 Created: pedido nuevo. 200 OK: ya existía uno con ese externoId y se regresa ese. 422: Enlace no habilitado, crédito insuficiente, plazo no disponible, etc. La respuesta es el pedido, en el formato de GET /pedidos/{externoId}.
Lista de pedidos
Sus pedidos, del más reciente al más antiguo. Todos los filtros son opcionales.
curl "https://marketplace.capta.best/api/tienda-externa/pedidos?estatus=autorizado&firmado=true" \
-H "X-Api-Key: $CAPTA_API_KEY"
| Parámetro | Tipo | Descripción |
|---|---|---|
estatus | opcional | pendiente, autorizado, enviado, pagado o cancelado. |
tipo | opcional | cliente o enlace. |
firmado | opcional | true (todos sus vales firmados) o false. |
desde, hasta | opcional | Fecha del pedido, YYYY-MM-DD, inclusive. |
pagina | opcional | Desde 1 (default 1). |
porPagina | opcional | 1 a 100 (default 50). |
{
"data": {
"total": 1,
"pagina": 1,
"porPagina": 50,
"pedidos": [
{
"externoId": "A-100234",
"pedidoId": 900001,
"fechaRegistro": "2026-11-11T10:15:00.123",
"tipo": "cliente",
"estatus": "autorizado",
"firmado": true,
"comprador": { "nombre": "MARIA PRUEBA LOPEZ", "celular": "6670000001" },
"enlaceNombre": "ANA ENLACE PRUEBA",
"entrega": { "calle": "Av. Obregón", "numeroExterior": "850", "numeroInterior": null, "colonia": "Centro", "codigoPostal": "80000", "municipio": "Culiacán", "estado": "Sinaloa", "referencias": "Portón negro" },
"total": 17264.52,
"pagoQuincenal": 987.22,
"folios": ["F900001", "F900002"],
"fechaEnvio": null,
"notasEnvio": null
}
]
}
}
total es el número de pedidos que cumplen los filtros en todas las páginas. Filtro inválido: 400.
Un pedido
Estado completo del pedido y de cada vale. Es el mismo objeto que regresan crear, enviado y cancelar, y el que viaja en los webhooks.
curl "https://marketplace.capta.best/api/tienda-externa/pedidos/A-100234" \
-H "X-Api-Key: $CAPTA_API_KEY"
{
"data": {
"externoId": "A-100234",
"pedidoId": 900001,
"estatus": "autorizado",
"tipo": "cliente",
"clienteId": 800001,
"enlaceId": 9001,
"comprador": { "nombre": "MARIA PRUEBA LOPEZ", "celular": "6670000001" },
"enlaceNombre": "ANA ENLACE PRUEBA",
"entrega": { "calle": "Av. Obregón", "numeroExterior": "850", "numeroInterior": null, "colonia": "Centro", "codigoPostal": "80000", "municipio": "Culiacán", "estado": "Sinaloa", "referencias": "Portón negro" },
"notas": "Entregar por la tarde",
"transportista": "ESTAFETA",
"subtotal": 9498.00,
"flete": 250.00,
"intereses": 7516.52,
"total": 17264.52,
"pagoQuincenal": 987.22,
"planElegido": true,
"firmado": false,
"fechaRegistro": "2026-11-11T10:15:00.123",
"fechaAutorizacion": "2026-11-11T11:02:41.507",
"fechaEnvio": null,
"notasEnvio": null,
"fechaCancelacion": null,
"motivoCancelacion": null,
"vales": [
{ "folio": "F900001", "plazo": 12, "subtotal": 499.00, "flete": 125.00, "total": 1010.88, "pagoQuincenal": 84.24, "firmado": true, "estatus": "activo", "skus": ["SOP-01"] },
{ "folio": "F900002", "plazo": 18, "subtotal": 8999.00, "flete": 125.00, "total": 16253.64, "pagoQuincenal": 902.98, "firmado": false, "estatus": "activo", "skus": ["TV-55"] }
]
}
}
| Campo | Valores |
|---|---|
estatus | pendiente, autorizado, enviado, pagado, cancelado. |
vales[].estatus | activo, canjeado (ya tiene calendario de pagos), pagado, cancelado. |
firmado | El comprador firmó todos los vales. |
clienteId | null en tipo enlace. |
comprador | El cliente, o la Enlace en tipo enlace. |
| Fechas | Hora local de México, sin zona. |
entrega es la dirección vigente. Mientras no firme, el cliente puede cambiarla en su app: surtan a esta, no a la que mandaron al crear el pedido.
Reportar el envío
Repórtenlo cuando el pedido salga. Requiere estatus autorizado y todos los vales firmados. Desde este momento corre el calendario de pagos del comprador.
curl -X POST "https://marketplace.capta.best/api/tienda-externa/pedidos/A-100234/enviado" \
-H "X-Api-Key: $CAPTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "notasEnvio": "ESTAFETA guía 1234567890, sale hoy" }'
| Campo | Tipo | Descripción |
|---|---|---|
notasEnvio | opcional | Máx. 200. Paquetería, guía o lo que quieran anotar; Capta lo ve en el pedido y regresa en notasEnvio. |
Respuesta: el pedido con estatus enviado. Si ya estaba enviado se regresa igual (200) sin cambiar nada: pueden reintentar. 422 si no está autorizado o falta alguna firma.
Cancelar
Solo mientras el pedido esté pendiente o autorizado y ningún vale esté firmado. Se libera el crédito y se avisa al comprador.
curl -X POST "https://marketplace.capta.best/api/tienda-externa/pedidos/A-100234/cancelar" \
-H "X-Api-Key: $CAPTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "motivo": "Sin existencia" }'
motivo es obligatorio (máx. 500). Respuesta: el pedido con estatus cancelado; si ya estaba cancelado se regresa igual (200). 422 si ya hay un vale firmado o ya se envió.
Webhooks
Capta les manda los cambios de sus pedidos a la URL (HTTPS) que nos indiquen. Junto con la URL les entregamos un secreto para validar la firma.
POST {su URL}
Content-Type: application/json
X-Capta-Evento: pedido.autorizado
X-Capta-Evento-Id: 5821
X-Capta-Timestamp: 1794412345
X-Capta-Firma: sha256=3f1c…e9
{ "evento": "pedido.autorizado", "valeFolio": null, "pedido": { …mismo objeto que GET /pedidos/{externoId}… } }
| Evento | Cuándo | valeFolio |
|---|---|---|
| pedido.autorizado | El Enlace autorizó y eligió el plan (montos finales). | null |
| vale.firmado | El comprador firmó un vale. | folio del vale |
| pedido.firmado | Ya firmó todos los vales: pueden surtir. | null |
| pedido.enviado | Se registró el envío. | null |
| pedido.pagado | El comprador terminó de pagar todos los vales. | null |
| pedido.cancelado | Lo cancelaron ustedes o el Enlace. | null |
- Respondan 2xx en menos de 15 segundos. Si tienen trabajo pesado, guárdenlo y procésenlo después.
- Si fallan, Capta reintenta con espera creciente (1 min, 5 min, 15 min, 30 min, 1 h, 3 h, 6 h…) hasta 8 intentos.
- Los eventos de un mismo pedido llegan en orden: mientras uno no se entregue, los siguientes de ese pedido esperan.
- Puede llegar el mismo evento más de una vez: usen
X-Capta-Evento-Idpara ignorar repetidos. pedidotrae el estado al momento del evento (enpedido.pagado, al momento de mandarlo). Guíense porX-Capta-Evento. Si se pierde algo,GET /pedidos/{externoId}siempre tiene el estado actual.
Validar la firma
X-Capta-Firma = sha256= + HMAC-SHA256 en hexadecimal (minúsculas), con su secreto como llave, de {X-Capta-Timestamp}.{cuerpo exacto}. Calculen la firma con el cuerpo crudo (antes de parsear el JSON), compárenla en tiempo constante y rechacen timestamps con más de 5 minutos de diferencia.
const crypto = require('crypto');
app.post('/webhooks/capta', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.get('X-Capta-Timestamp');
const firma = req.get('X-Capta-Firma') || '';
const esperada = 'sha256=' + crypto
.createHmac('sha256', process.env.CAPTA_WEBHOOK_SECRET)
.update(`${timestamp}.${req.body.toString('utf8')}`)
.digest('hex');
const valida = firma.length === esperada.length
&& crypto.timingSafeEqual(Buffer.from(firma), Buffer.from(esperada));
const reciente = Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300;
if (!valida || !reciente) return res.sendStatus(401);
const { evento, valeFolio, pedido } = JSON.parse(req.body.toString('utf8'));
// guardar / encolar y responder rápido
res.sendStatus(200);
});
static bool FirmaValida(string secreto, string timestamp, string cuerpo, string firma)
{
var hash = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secreto), Encoding.UTF8.GetBytes($"{timestamp}.{cuerpo}"));
var esperada = "sha256=" + Convert.ToHexString(hash).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(Encoding.ASCII.GetBytes(esperada), Encoding.ASCII.GetBytes(firma ?? ""))
&& Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(timestamp)) <= 300;
}
Compradores de prueba
Solo en el simulador del sandbox. Por el túnel se usan los clientes y Enlaces de pruebas que les pase Capta.
| Celular | tipo | Quién | Para probar |
|---|---|---|---|
6670000001 | cliente | MARIA PRUEBA LOPEZ (crédito $15,000) | El flujo normal |
6670000002 | cliente | JUAN PRUEBA SIN TIENDA | puedeRecibirPedidos: false; crear → 422 |
6670000003 | cliente | ROSA PRUEBA POCO CREDITO ($500) | cabeEnCredito: false; crear → 422 |
6670000004 | cliente | PEDRO PRUEBA SIN ENLACE | 422 sin Enlace asignado |
6670000009 | cliente | (dos clientes con el mismo celular) | 422 más de un cliente |
6660000001 | enlace | ANA ENLACE PRUEBA (crédito personal $8,000) | Pedidos tipo enlace |
6660000002 | enlace | LUCIA ENLACE SIN TIENDA | Crear tipo enlace → 422 |
| cualquier otro | 404 |
Acciones del simulador
En la vida real el Enlace autoriza y el comprador firma en las apps de Capta. En el simulador lo disparan ustedes (con su X-Api-Key); cada acción manda el mismo webhook que mandaría Capta (a la URL de webhooks que registraron con Capta, firmado con su secreto) y regresa el pedido.
| Ruta | Cuerpo | Simula |
|---|---|---|
/sandbox/pedidos/{id}/autorizar | { "porcentaje": 16 }16, 17, 18 o 20 (default) | El Enlace autoriza (tipo cliente) y elige el plan |
/sandbox/pedidos/{id}/firmar | { "folio": "F900001" } opcional | Cliente: firma ese vale o el siguiente. Enlace: firma todo y queda autorizado |
/sandbox/pedidos/{id}/rechazar | { "motivo": "..." } | El Enlace rechaza el pedido |
/sandbox/pedidos/{id}/cambiar-entrega | { "calle": "...", "codigoPostal": "..." } | El cliente cambia la dirección (sin webhook) |
/sandbox/pedidos/{id}/pagar | El comprador terminó de pagar | |
/sandbox/webhooks | Últimos webhooks y si se entregaron | |
/sandbox/reiniciar | Borra los pedidos simulados |
H="X-Api-Key: $CAPTA_API_KEY"
curl -X POST https://marketplace.capta.best/sandbox/pedidos/A-100234/autorizar -H "$H" -H "Content-Type: application/json" -d '{ "porcentaje": 16 }'
curl -X POST https://marketplace.capta.best/sandbox/pedidos/A-100234/firmar -H "$H"
curl -X POST https://marketplace.capta.best/sandbox/pedidos/A-100234/firmar -H "$H"
curl -X POST https://marketplace.capta.best/api/tienda-externa/pedidos/A-100234/enviado \
-H "$H" -H "Content-Type: application/json" -d '{ "notasEnvio": "ESTAFETA 123" }'
curl -X POST https://marketplace.capta.best/sandbox/pedidos/A-100234/pagar -H "$H"