Conecta tu sistema con Vesvir
Vesvir atiende, cotiza y cobra por WhatsApp con un asistente de IA y tu equipo de ventas. Esta API mantiene a Vesvir al día con tu ERP, tu inventario o tu CRM, y le regresa a tu sistema lo que se vende.
| Dirección | Qué viaja | Para qué |
|---|---|---|
| Tu sistema → Vesvir | Productos, precios, existencias por sucursal | El asistente cotiza con tu precio y tu existencia reales. |
| Tu sistema → Vesvir | Clientes y su vendedor asignado (cartera), crédito | Cada cliente llega con su vendedor; el asistente sabe quién es. |
| Vesvir → tu sistema | Cotizaciones y pagos (aviso al instante + consulta) | Das de alta el pedido o la factura en tu ERP sin capturar nada. |
| Tu sistema → Vesvir | Estado del pedido (facturada, surtida, pagada) | El vendedor y el asistente ven en qué va cada venta. |
URL base de todas las llamadas:
https://api.vesvir.com/functions/v1/vesvir-api/v1
Llave y autenticación
La llave la genera el administrador del negocio desde Vesvir (o la pide al equipo de Vesvir). Se ve una sola vez: guárdala en tu bóveda de secretos. Mándala en cada llamada:
Authorization: Bearer vsv_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Cada llave tiene permisos por recurso (productos, clientes, cotizaciones, pagos, webhooks, mapeos). Si una llave se filtra, desactívala desde el panel y genera otra; las demás siguen funcionando.
GET/ping
curl https://api.vesvir.com/functions/v1/vesvir-api/v1/ping \
-H "Authorization: Bearer $VESVIR_LLAVE"
# → {"ok":true,"negocio":"Refaccionaria Ejemplo","permisos":["productos",…],"version":"v1"}
Reglas generales
- JSON en UTF-8, en el cuerpo y en las respuestas. Fechas en ISO 8601 (
2026-10-04T10:00:00-06:00). - Hasta 1,000 registros por llamada. Para un catálogo más grande, manda lotes de 1,000.
- 120 llamadas por minuto por llave. Si te pasas recibes
429; espera unos segundos y reintenta. - Simulacro: agrega
?dry_run=1a cualquier escritura para validar sin guardar nada. Úsalo en tus primeras pruebas. - Idempotente: mandar el mismo producto o cliente dos veces lo actualiza; no lo duplica.
- Solo POST: si tu sistema no puede mandar
PUToPATCH, usaPOSTen la misma ruta. - Toda llamada queda en la bitácora que el negocio ve en su panel.
Checklist de conexión (≈1 día de TI)
- Pide la llave al administrador y prueba
GET /ping. - Manda 10 productos con
?dry_run=1, revisa errores y luego sindry_run. - Programa la sincronización de productos: completa una vez al día y, si puedes, cada vez que cambie un precio o una existencia.
- Carga tu cartera de clientes con su vendedor (
PUT /clientes). - Registra tu webhook (
POST /webhooks), guarda el secreto y pruébalo conPOST /webhooks/{id}/probar. - Al recibir
cotizacion.creadaopago.recibido, da de alta el pedido en tu ERP y regresa el estado conPATCH /cotizaciones/{folio}.
Productos y existencias
PUT/productos
Da de alta o actualiza por sku. Lo que ya existe conserva lo que Vesvir le agregó (fotos, notas).
| Campo | Tipo | Descripción |
|---|---|---|
sku * | texto | Tu clave única del producto. |
nombre * | texto | Como lo verá el cliente. |
precio | número | Precio de venta. Acepta "1,250.50". |
moneda | texto | MXN por defecto. |
existencias | objeto | Por sucursal: {"Monterrey": 5, "Saltillo": 0}. La existencia total se calcula sola. |
existencia | número | Total, si no manejas sucursales. |
disponible | sí/no | Si no mandas existencias. |
marca, categoria, subcategoria, unidad | texto | Ayudan al asistente a encontrar el producto. |
descripcion | texto | Ficha técnica corta. |
codigo_fabricante, codigo_proveedor | texto | El cliente puede buscar por estas claves. |
listas_precio | objeto | {"mayoreo": 980, "distribuidor": 900} |
activo | sí/no | false deja de ofrecerse. |
curl -X PUT https://api.vesvir.com/functions/v1/vesvir-api/v1/productos \
-H "Authorization: Bearer $VESVIR_LLAVE" -H "Content-Type: application/json" \
-d '{"productos":[
{"sku":"BAL-001","nombre":"Balata delantera Brembo Versa 2015","precio":1250.50,
"marca":"Brembo","existencias":{"Monterrey":5,"Saltillo":0}},
{"sku":"FIL-10","nombre":"Filtro de aceite","precio":95,"disponible":true}
]}'
# → {"ok":true,"creados":2,"actualizados":0,"con_error":0,"errores":[]}
GET/productos?sku=BAL-001,FIL-10&desde=2026-10-01&limite=100&pagina=1
POST/productos/desactivar {"skus":["KIT-OLD"]}
Clientes y cartera
PUT/clientes
Por id_externo (tu clave de cliente) o rfc. Con vendedor, el cliente pasa directo con esa persona cuando escribe.
| Campo | Tipo | Descripción |
|---|---|---|
id_externo * (o rfc) | texto | Tu clave del cliente. |
nombre * | texto | Razón social o nombre. |
rfc | texto | Se valida el formato. |
telefonos, correos | lista | Con ellos Vesvir lo reconoce cuando escribe por WhatsApp. |
vendedor | texto | Correo o nombre del vendedor en Vesvir, o su clave de vendedor. |
segmento, lista_precio, descuento_pct | Condiciones comerciales. | |
credito | objeto | {"limite":50000,"dias":30,"saldo":12000} |
ultima_compra | fecha | AAAA-MM-DD. Si tu cuenta tiene la regla "un cliente que no compra desde hace N meses se atiende como nuevo", el cliente con fecha más vieja llega al vendedor en turno en vez de a su ejecutivo, y lo recupera cuando vuelve a comprar. |
activo | sí/no |
curl -X PUT https://api.vesvir.com/functions/v1/vesvir-api/v1/clientes \
-H "Authorization: Bearer $VESVIR_LLAVE" -H "Content-Type: application/json" \
-d '{"clientes":[{"id_externo":"C-100","nombre":"Taller El Rayo SA de CV","rfc":"TER010101AB1",
"telefonos":["8110000001"],"vendedor":"mario@tuempresa.mx",
"credito":{"limite":50000,"dias":30}}]}'
# → {"ok":true,"creados":1,"actualizados":0,"vendedores_no_encontrados":[]}
GET/clientes?id_externo=C-100&rfc=&desde=&limite=&pagina=
Vendedores (equipo sin acceso al panel)
PUT/vendedores
Da de alta a tus vendedores tal como están en tu sistema, sin que entren al panel: así cada cliente llega con su vendedor y a él le llega el aviso al celular o al correo cuando el asistente lo atiende o le cotiza. No ocupan usuario de tu plan ni entran a la rotación de clientes nuevos. Cuando quieras darle acceso a alguien, lo invitas desde Equipo.
| Campo | Tipo | Descripción |
|---|---|---|
id_externo * | texto | Tu clave del vendedor. Es la misma que mandas en clientes[].vendedor. Si la misma persona tiene varias claves con el mismo correo, queda un solo vendedor con todas sus claves. |
nombre * | texto | |
correo | texto | Para el aviso por correo. Si no tiene, se da de alta igual (sin correo). |
telefono | texto | Celular para el aviso por WhatsApp; se queda con 10 dígitos (acepta 52, extensiones y varios números: toma el primero). |
puesto, sucursal, ciudad, estado | texto | Solo informativos. |
activo | sí/no | Desactiva solo a los vendedores que dio de alta la API. |
curl -X PUT https://api.vesvir.com/functions/v1/vesvir-api/v1/vendedores \
-H "Authorization: Bearer $VESVIR_LLAVE" -H "Content-Type: application/json" \
-d '{"vendedores":[{"id_externo":"V-57","nombre":"Mario Pérez","correo":"mario@tuempresa.mx",
"telefono":"81 1234 5678","puesto":"Vendedor","sucursal":"Monterrey"}]}'
# → {"ok":true,"creados":1,"actualizados":0,"con_celular":1,"con_correo":1}
GET/vendedores
Tu propio formato (mapeo)
Si tu sistema ya tiene su formato (SAP Business One, Odoo, Microsip, un JSON propio), no lo cambies: Vesvir lo traduce.
- POST
/mapeos/productos/sugerircon{"ejemplo": [ …uno o tres registros tal como los manda tu sistema… ]}. Vesvir propone el mapeo y te enseña cómo quedaría (vista_previa). - Revísalo y guárdalo: PUT
/mapeos/productoscon{"mapa": {…}}. - Desde ahí manda tus datos tal cual: POST
/ingesta/productoscon tu lista.
# ejemplo con SAP Business One
{"mapa":{
"sku":"ItemCode", "nombre":"ItemName",
"precio":{"ruta":"ItemPrices.PriceList1.Price","tipo":"numero"},
"marca":"Manufacturer", "categoria":"ItemsGroupName", "unidad":"InventoryUOM",
"activo":{"ruta":"Valid","tipo":"booleano"},
"existencias":{"ruta":"ItemWarehouseInfoCollection","clave":"WarehouseCode","valor":"InStock"}
}}
Igual con clientes: /mapeos/clientes/sugerir, /mapeos/clientes, /ingesta/clientes. Los sí/no se entienden en cualquier forma (true, 1, "Sí", "activo", "tYES").
Cotizaciones
Las crea el asistente o el vendedor en Vesvir, con tus precios. Las recibes al instante por webhook o las consultas:
GET/cotizaciones?desde=2026-10-01&estado=enviada,aceptada
GET/cotizaciones/{folio}
{"ok":true,"cotizacion":{"folio":"COT-2026-0001","estado":"enviada",
"cliente":{"nombre":"Taller El Rayo SA de CV","telefono":"528110000001","rfc":"TER010101AB1"},
"conceptos":[{"sku":"BAL-001","descripcion":"Balata delantera…","cantidad":2,"precio_unitario":1250.5,"importe":2501}],
"subtotal":2501,"iva":400.16,"total":2901.16,"moneda":"MXN","vigencia":"2026-10-11","pdf":"https://…"}}
PATCH/cotizaciones/{folio}
Regresa lo que pasó en tu ERP. estado: aceptada, facturada, surtida, enviada_cliente, entregada, cancelada, pagada.
{"estado":"facturada","factura":{"folio":"F-5501","uuid":"1A2B3C4D-…"},"notas":"Sale mañana por paquetería"}
Pagos
Si el pago llega por fuera (transferencia a tu cuenta, efectivo en mostrador), confírmalo y Vesvir hace lo demás: le avisa al cliente, marca la venta como ganada y cierra el seguimiento.
POST/pagos
{"folio":"COT-2026-0001","monto":2901.16,"referencia":"SPEI 123456","metodo":"transferencia"}
Avisos (webhooks)
Vesvir le avisa a tu sistema en cuanto pasa algo. Registra una URL https:
POST/webhooks {"url":"https://erp.tuempresa.mx/vesvir","eventos":["cotizacion.creada","pago.recibido"]}
La respuesta trae el secreto (whsec_…) con el que verificas la firma. Se ve una sola vez.
| Evento | Cuándo |
|---|---|
cotizacion.creada | El asistente o un vendedor manda una cotización. |
cotizacion.actualizada | Cambia de estado (aceptada, pagada, cancelada…). Trae estado_anterior. |
pago.recibido | Se confirma un pago (link, SPEI con conciliación, comprobante validado o tu POST /pagos). |
Cada aviso es un POST con este cuerpo:
{"id":"0b6e…","tipo":"cotizacion.creada","creado":"2026-10-04T05:22:10Z",
"datos":{"folio":"COT-2026-0001","estado":"enviada","total":2901.16,"cliente":{…},"conceptos":[…],"origen":"vesvir"}}
- Contesta 2xx en menos de 10 segundos. Haz el trabajo pesado después: guarda el aviso y procésalo aparte.
- Si no contestas 2xx, reintentamos a 1 min, 5 min, 30 min, 2 h y 12 h. Después de 20 fallos seguidos el webhook se desactiva.
- Usa
idpara no procesar dos veces el mismo aviso. origen: "api"significa que el cambio lo hizo tu propio sistema; puedes ignorarlo.- Prueba tu URL con POST
/webhooks/{id}/probar. GET/webhookslista; DELETE/webhooks/{id}desactiva.
Verificar la firma
Cada aviso trae X-Vesvir-Firma: t=<segundos>,v1=<hex>, un HMAC-SHA256 de "<t>.<cuerpo crudo>" con tu secreto. Rechaza avisos con firma inválida o con t de hace más de 5 minutos.
Node.js
import crypto from "node:crypto";
function firmaValida(cuerpoCrudo, encabezado, secreto) {
const { t, v1 } = Object.fromEntries(encabezado.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const esperado = crypto.createHmac("sha256", secreto).update(`${t}.${cuerpoCrudo}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(v1));
}
Python
import hmac, hashlib, time
def firma_valida(cuerpo_crudo: bytes, encabezado: str, secreto: str) -> bool:
partes = dict(p.split("=", 1) for p in encabezado.split(","))
if abs(time.time() - int(partes["t"])) > 300:
return False
esperado = hmac.new(secreto.encode(), f'{partes["t"]}.'.encode() + cuerpo_crudo, hashlib.sha256).hexdigest()
return hmac.compare_digest(esperado, partes["v1"])
C# (.NET)
static bool FirmaValida(string cuerpo, string encabezado, string secreto) {
var p = encabezado.Split(',').Select(x => x.Split('=', 2)).ToDictionary(x => x[0], x => x[1]);
if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(p["t"])) > 300) return false;
using var h = new System.Security.Cryptography.HMACSHA256(System.Text.Encoding.UTF8.GetBytes(secreto));
var esperado = Convert.ToHexString(h.ComputeHash(System.Text.Encoding.UTF8.GetBytes($"{p["t"]}.{cuerpo}"))).ToLower();
return System.Security.Cryptography.CryptographicOperations.FixedTimeEquals(
System.Text.Encoding.UTF8.GetBytes(esperado), System.Text.Encoding.UTF8.GetBytes(p["v1"]));
}
Errores
Siempre {"ok":false,"error":{"codigo":"…","mensaje":"…"}}. En lotes, los registros con problema vienen en errores con su posición (i) y los demás sí se guardan.
| HTTP | codigo | Qué hacer |
|---|---|---|
| 400 | json_invalido, sin_datos, estado_invalido | Revisa el cuerpo contra esta guía. |
| 401 | sin_llave, llave_invalida | Revisa el encabezado Authorization o genera otra llave. |
| 403 | sin_permiso | La llave no tiene permiso para ese recurso. |
| 404 | no_existe, ruta_desconocida | Folio o ruta incorrectos. |
| 409 | sin_mapeo | Guarda tu mapeo antes de usar /ingesta. |
| 413 | lote_grande | Divide en lotes de 1,000. |
| 429 | demasiadas_llamadas | Espera y reintenta (120 por minuto). |
| 500/502 | error_interno | Reintenta; si sigue, avísanos con la hora de la llamada. |
Soporte
Escríbele a tu contacto en Vesvir (o a Buddy por WhatsApp) con el nombre del negocio y la hora de la llamada. Cada llamada queda en la bitácora, así que vemos exactamente qué llegó. ¿Tu ERP necesita un conector a la medida (Microsip, Aspel, CONTPAQi con base de datos local)? Pídelo: lo armamos como proyecto Enterprise.