[{"data":1,"prerenderedAt":1302},["ShallowReactive",2],{"docs-nav-docs_es":3,"docs-search-docs_es":76,"doc-docs_es-\u002Fdocs\u002Fapi\u002Fwebhooks":461,"surround-docs_es-\u002Fdocs\u002Fapi\u002Fwebhooks":1297},[4],{"title":5,"path":6,"stem":7,"children":8,"page":42},"Docs","\u002Fdocs","docs",[9,13,17,21,43,47,68,72],{"title":10,"path":11,"stem":12},"Introducción","\u002Fdocs\u002Fintroduction","docs\u002F1.introduction",{"title":14,"path":15,"stem":16},"Inicio rápido","\u002Fdocs\u002Fquickstart","docs\u002F2.quickstart",{"title":18,"path":19,"stem":20},"Cómo funciona","\u002Fdocs\u002Fhow-it-works","docs\u002F3.how-it-works",{"title":22,"path":23,"stem":24,"children":25,"page":42},"Conceptos","\u002Fdocs\u002Fconcepts","docs\u002F4.concepts",[26,30,34,38],{"title":27,"path":28,"stem":29},"Workspaces y recursos","\u002Fdocs\u002Fconcepts\u002Fworkspaces-and-resources","docs\u002F4.concepts\u002F1.workspaces-and-resources",{"title":31,"path":32,"stem":33},"Servicios y disponibilidad","\u002Fdocs\u002Fconcepts\u002Fservices-and-availability","docs\u002F4.concepts\u002F2.services-and-availability",{"title":35,"path":36,"stem":37},"Reservas y pre-reservas","\u002Fdocs\u002Fconcepts\u002Fbookings-and-holds","docs\u002F4.concepts\u002F3.bookings-and-holds",{"title":39,"path":40,"stem":41},"Automatizaciones","\u002Fdocs\u002Fconcepts\u002Fautomations","docs\u002F4.concepts\u002F4.automations",false,{"title":44,"path":45,"stem":46},"WhatsApp e IA","\u002Fdocs\u002Fwhatsapp-and-ai","docs\u002F5.whatsapp-and-ai",{"title":48,"path":49,"stem":50,"children":51,"page":42},"API","\u002Fdocs\u002Fapi","docs\u002F6.api",[52,56,60,64],{"title":53,"path":54,"stem":55},"Autenticación","\u002Fdocs\u002Fapi\u002Fauthentication","docs\u002F6.api\u002F1.authentication",{"title":57,"path":58,"stem":59},"Convenciones","\u002Fdocs\u002Fapi\u002Fconventions","docs\u002F6.api\u002F2.conventions",{"title":61,"path":62,"stem":63},"Endpoints","\u002Fdocs\u002Fapi\u002Fendpoints","docs\u002F6.api\u002F3.endpoints",{"title":65,"path":66,"stem":67},"Webhooks","\u002Fdocs\u002Fapi\u002Fwebhooks","docs\u002F6.api\u002F4.webhooks",{"title":69,"path":70,"stem":71},"MCP y agentes de IA","\u002Fdocs\u002Fmcp","docs\u002F7.mcp",{"title":73,"path":74,"stem":75},"Recetas por negocio","\u002Fdocs\u002Frecipes","docs\u002F8.recipes",[77,81,87,92,97,102,105,110,115,120,125,130,135,140,145,148,153,158,163,168,171,176,181,186,191,194,199,204,209,214,219,222,227,232,237,242,247,250,255,260,265,270,275,278,283,288,293,298,303,308,311,316,321,326,329,334,339,344,349,354,357,362,367,372,377,382,387,390,395,400,405,410,413,418,423,428,433,436,441,446,451,456],{"id":11,"title":10,"titles":78,"content":79,"level":80},[],"Qué es Wagend, para quién es y los bloques que vas a usar. Wagend es un motor de reservas con WhatsApp + IA como canal principal. Tus clientes reservan conversando; el sistema ofrece solo horarios que existen de verdad, los pre-reserva, los confirma y envía recordatorios. El negocio gestiona todo desde un panel, y los desarrolladores integran por API REST, webhooks y servidor MCP. Wagend está en desarrollo. Esta documentación describe el contrato objetivo (borrador de la API v1). Los endpoints todavía pueden cambiar antes del piloto.",1,{"id":82,"title":83,"titles":84,"content":85,"level":86},"\u002Fdocs\u002Fintroduction#para-quién-es","Para quién es",[10],"Cualquier negocio que vende el tiempo de alguien o de algo: NegocioQué se reservaBarberíaUn barbero por 30–45 minutosEstéticaUn profesional y una máquina y una cabina, al mismo tiempoClínicaUn médico y un consultorioRestauranteCubiertos en el salón según el tamaño del grupoEntregasCapacidad en una ventana de 2 horas para una zonaClases, canchas, salasUn lugar en una clase o una cancha por hora",2,{"id":88,"title":89,"titles":90,"content":91,"level":86},"\u002Fdocs\u002Fintroduction#bloques-principales","Bloques principales",[10],"Organización → tu cuenta. Tiene uno o más workspaces (un negocio o sede).Recurso → lo que se consume: un barbero, una mesa, una máquina de láser, una sala, una zona de entrega.Servicio → lo que reserva el cliente. Declara sus requisitos: qué recursos necesita, y cuántos, al mismo tiempo.Horario → cuándo está disponible cada recurso.Reserva → pasa por una pre-reserva (10 minutos) antes de quedar confirmada.Automatización → recordatorios, confirmaciones y avisos disparados por eventos de la reserva. Leé Workspaces y recursos para el modelo completo.",{"id":93,"title":94,"titles":95,"content":96,"level":86},"\u002Fdocs\u002Fintroduction#canales","Canales",[10],"Todos los canales usan el mismo motor: un horario reservado por WhatsApp desaparece al instante de la página de reserva y de la API. WhatsApp con asistente de IA (texto y audio).Página pública de reserva y widget embebible.Panel para el equipo (agenda del día, inbox, calendario, configuración).API REST, webhooks y MCP para desarrolladores y agentes de IA.",{"id":98,"title":99,"titles":100,"content":101,"level":86},"\u002Fdocs\u002Fintroduction#próximos-pasos","Próximos pasos",[10],"Inicio rápido: hacé tu primera reserva por API en 5 minutos.Cómo funciona: la arquitectura y la vida de un mensaje.MCP: conectá un agente de IA a Wagend.",{"id":15,"title":14,"titles":103,"content":104,"level":80},[],"Creá tu primera reserva por API en cinco minutos con una clave de prueba. Esta guía usa una clave de prueba (wg_test_...). Las claves de prueba trabajan contra una copia sandbox de tu workspace: no se envían mensajes de WhatsApp ni se hacen pagos reales. URL base: https:\u002F\u002Fapi.wagend.app\u002Fv1. Cada request necesita Authorization: Bearer \u003Cclave>.",{"id":106,"title":107,"titles":108,"content":109,"level":86},"\u002Fdocs\u002Fquickstart#_1-generá-una-clave-de-prueba","1. Generá una clave de prueba",[14],"En el panel, andá a Desarrolladores → Claves de API → Nueva clave, elegí Prueba y marcá los scopes slots:read, bookings:write y config:read. La clave se muestra una sola vez. export WAGEND_KEY=\"wg_test_xxxxxxxx_xxxxxxxxxxxxxxxxxxxx\"",{"id":111,"title":112,"titles":113,"content":114,"level":86},"\u002Fdocs\u002Fquickstart#_2-verificá-quién-sos","2. Verificá quién sos",[14],"curl https:\u002F\u002Fapi.wagend.app\u002Fv1\u002Fme -H \"Authorization: Bearer $WAGEND_KEY\" {\n  \"workspace\": { \"id\": \"ws_9f2c\", \"name\": \"Barbería Centro\", \"timezone\": \"America\u002FSao_Paulo\", \"locale\": \"es\", \"currency\": \"BRL\" },\n  \"scopes\": [\"slots:read\", \"bookings:write\", \"config:read\"],\n  \"mode\": \"test\"\n} El workspace siempre sale de la clave. Nunca enviás un id de workspace.",{"id":116,"title":117,"titles":118,"content":119,"level":86},"\u002Fdocs\u002Fquickstart#_3-listá-los-servicios","3. Listá los servicios",[14],"curl https:\u002F\u002Fapi.wagend.app\u002Fv1\u002Fservices -H \"Authorization: Bearer $WAGEND_KEY\" {\n  \"data\": [\n    { \"id\": \"svc_corte\", \"name\": \"Corte\", \"duration_min\": 30, \"price_cents\": 5000, \"currency\": \"BRL\",\n      \"requirements\": [{ \"resource_group_id\": \"grp_barbers\", \"units\": 1 }] }\n  ]\n}",{"id":121,"title":122,"titles":123,"content":124,"level":86},"\u002Fdocs\u002Fquickstart#_4-buscá-horarios-disponibles","4. Buscá horarios disponibles",[14],"curl \"https:\u002F\u002Fapi.wagend.app\u002Fv1\u002Fslots?service_id=svc_corte&from=2026-10-15T08:00:00-03:00&to=2026-10-15T13:00:00-03:00\" \\\n  -H \"Authorization: Bearer $WAGEND_KEY\" {\n  \"data\": [\n    { \"start\": \"2026-10-15T09:00:00-03:00\", \"end\": \"2026-10-15T09:30:00-03:00\", \"resource_ids\": [\"res_juan\"] },\n    { \"start\": \"2026-10-15T09:45:00-03:00\", \"end\": \"2026-10-15T10:15:00-03:00\", \"resource_ids\": [\"res_juan\"] }\n  ],\n  \"unavailable_reason\": null\n}",{"id":126,"title":127,"titles":128,"content":129,"level":86},"\u002Fdocs\u002Fquickstart#_5-pre-reservá-el-horario","5. Pre-reservá el horario",[14],"La pre-reserva (hold) guarda el horario por 10 minutos mientras juntás los datos del cliente. Enviá siempre un Idempotency-Key para que los reintentos sean seguros. curl -X POST https:\u002F\u002Fapi.wagend.app\u002Fv1\u002Fholds \\\n  -H \"Authorization: Bearer $WAGEND_KEY\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{ \"service_id\": \"svc_corte\", \"start\": \"2026-10-15T09:45:00-03:00\", \"resource_ids\": [\"res_juan\"] }' { \"id\": \"bkg_7Qx1\", \"status\": \"held\", \"expires_at\": \"2026-10-14T15:30:00-03:00\", \"start\": \"2026-10-15T09:45:00-03:00\" }",{"id":131,"title":132,"titles":133,"content":134,"level":86},"\u002Fdocs\u002Fquickstart#_6-confirmá","6. Confirmá",[14],"curl -X POST https:\u002F\u002Fapi.wagend.app\u002Fv1\u002Fholds\u002Fbkg_7Qx1\u002Fconfirm \\\n  -H \"Authorization: Bearer $WAGEND_KEY\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{ \"customer\": { \"name\": \"Carlos\", \"phone\": \"+5491155550000\", \"locale\": \"es\" } }' { \"id\": \"bkg_7Qx1\", \"status\": \"confirmed\", \"start\": \"2026-10-15T09:45:00-03:00\", \"source\": \"api\" } Si el servicio pide seña por Pix, el estado queda en pending_payment y la respuesta trae el código Pix. Pasa a confirmed cuando llega el pago.",{"id":136,"title":137,"titles":138,"content":139,"level":86},"\u002Fdocs\u002Fquickstart#el-mismo-flujo-en-javascript","El mismo flujo en JavaScript",[14],"const api = (path: string, init: RequestInit = {}) =>\n  fetch(`https:\u002F\u002Fapi.wagend.app\u002Fv1${path}`, {\n    ...init,\n    headers: { Authorization: `Bearer ${process.env.WAGEND_KEY}`, 'Content-Type': 'application\u002Fjson', ...init.headers },\n  }).then((r) => r.json())\n\nconst { data: slots } = await api(`\u002Fslots?service_id=svc_corte&from=${from}&to=${to}`)\nconst hold = await api('\u002Fholds', {\n  method: 'POST',\n  headers: { 'Idempotency-Key': crypto.randomUUID() },\n  body: JSON.stringify({ service_id: 'svc_corte', start: slots[0].start }),\n})\nconst booking = await api(`\u002Fholds\u002F${hold.id}\u002Fconfirm`, {\n  method: 'POST',\n  headers: { 'Idempotency-Key': crypto.randomUUID() },\n  body: JSON.stringify({ customer: { name: 'Carlos', phone: '+5491155550000' } }),\n})",{"id":141,"title":142,"titles":143,"content":144,"level":86},"\u002Fdocs\u002Fquickstart#siguientes-pasos","Siguientes pasos",[14],"Reaccioná a las reservas con webhooks.Mirá todos los endpoints en la referencia. html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"id":19,"title":18,"titles":146,"content":147,"level":80},[],"Arquitectura, principios y la vida de un mensaje de WhatsApp dentro de Wagend.",{"id":149,"title":150,"titles":151,"content":152,"level":86},"\u002Fdocs\u002Fhow-it-works#principios","Principios",[18],"Núcleo determinístico, IA como intérprete. La IA entiende al cliente y llama herramientas. El motor calcula disponibilidad, pre-reserva, confirma y cobra. La IA nunca inventa un horario ni un precio.La base de datos garantiza las reglas. El sobreturno es imposible porque PostgreSQL rechaza asignaciones superpuestas, no porque la app \"chequea antes\".API primero. Panel, página de reserva, bot de WhatsApp y MCP usan los mismos servicios. Si algo no se puede hacer por API, falta en la API.La credencial define el negocio. Una clave o sesión siempre pertenece a un workspace.",{"id":154,"title":155,"titles":156,"content":157,"level":86},"\u002Fdocs\u002Fhow-it-works#arquitectura","Arquitectura",[18],"Canales:    WhatsApp · Página de reserva · Panel · REST \u002F MCP\n                 │\nGateway:    credencial → workspace · scopes · límites · webhooks firmados\n                 │\nNúcleo:     conversaciones + IA │ motor de agenda │ automatizaciones │ pagos │ conocimiento\n                 │\nDatos:      PostgreSQL (seguridad por fila, constraints de exclusión, jobs) · Redis\nExternos:   modelos de IA (OpenRouter) · transcripción de audio · proveedor de WhatsApp · Pix",{"id":159,"title":160,"titles":161,"content":162,"level":86},"\u002Fdocs\u002Fhow-it-works#la-vida-de-un-mensaje","La vida de un mensaje",[18],"Llega. El proveedor de WhatsApp llama a nuestro webhook. Verificamos la firma HMAC, descartamos duplicados por id de mensaje y lo guardamos. Respondemos en menos de 200 ms.Se entiende. Los audios se transcriben. Identificamos cliente y conversación. Si una persona tomó el control, el bot no responde.La IA pide. El modelo recibe el contexto del negocio, la fecha y hora locales, un estado estructurado de la conversación y los últimos mensajes. Solo puede llamar herramientas como find_slots, hold_slot o confirm_hold.El motor decide. Cada llamada se valida contra un schema estricto y las reglas del negocio, y después se ejecuta. Los errores vuelven como datos estructurados (por ejemplo slot_taken con alternativas) para que la IA vuelva a preguntar.Respuesta exacta. Fechas, horas, servicios, precios y direcciones salen de plantillas completadas con el resultado real.Eventos. booking.confirmed y compañía disparan automatizaciones (recordatorios) y tus webhooks.",{"id":164,"title":165,"titles":166,"content":167,"level":86},"\u002Fdocs\u002Fhow-it-works#a-dónde-seguir","A dónde seguir",[18],"Reservas y pre-reservas explica las garantías.WhatsApp e IA explica qué puede y qué no puede hacer la IA.",{"id":28,"title":27,"titles":169,"content":170,"level":80},[],"Organizaciones, workspaces, roles, recursos, modos de capacidad y grupos de recursos.",{"id":172,"title":173,"titles":174,"content":175,"level":86},"\u002Fdocs\u002Fconcepts\u002Fworkspaces-and-resources#organización-y-workspaces","Organización y workspaces",[27],"Una organización es la cuenta que paga. Contiene uno o más workspaces. Un workspace es un negocio o sede con sus propios: zona horaria, idioma y moneda,número(s) de WhatsApp,bot, servicios, recursos y equipo. Una cadena de tres barberías es una organización con tres workspaces.",{"id":177,"title":178,"titles":179,"content":180,"level":86},"\u002Fdocs\u002Fconcepts\u002Fworkspaces-and-resources#roles","Roles",[27],"RolPuedeownerTodo, en todos los workspaces de la organizaciónmanagerTodo dentro de un workspacestaffVer y gestionar solo sus propios recursos (por ejemplo, el barbero ve solo su agenda)viewerSolo lectura El equipo también puede actuar por WhatsApp después de vincular su número con un código de un solo uso. Las acciones de administrador por WhatsApp siempre piden confirmación SÍ\u002FNO.",{"id":182,"title":183,"titles":184,"content":185,"level":86},"\u002Fdocs\u002Fconcepts\u002Fworkspaces-and-resources#recursos","Recursos",[27],"Un recurso es cualquier cosa que una reserva consume. kindEjemplosstaffBarbero, médico, esteticista, instructorspaceSala, consultorio, cabina, salónequipmentMáquina de láser, sillón, canchatableMesa de restaurante (modo mesas)vehicleCamioneta, equipo de repartozoneZona de entrega por código postal Cada recurso tiene un modo de capacidad: exclusive — atiende una reserva a la vez (un barbero, una sala, una máquina).pooled — tiene capacidad en unidades (40 cubiertos por turno, 12 lugares en una clase, 10 entregas por ventana).",{"id":187,"title":188,"titles":189,"content":190,"level":86},"\u002Fdocs\u002Fconcepts\u002Fworkspaces-and-resources#grupos-de-recursos","Grupos de recursos",[27],"Un grupo de recursos es un conjunto intercambiable (\"cualquier barbero\", \"máquinas de láser\"). Los servicios piden grupos, no recursos individuales, para que el motor elija uno. La estrategia de asignación decide cuál: assignmentComportamientocustomer_choiceElige el cliente (por ejemplo \"con Juan\"); si le da igual, usa otra estrategiaround_robinRota entre los miembrosleast_busyElige al miembro con menos reservas en el díafixedSiempre el mismo miembro (una sola máquina, una sola sala) {\n  \"name\": \"Barberos\",\n  \"assignment\": \"customer_choice\",\n  \"member_ids\": [\"res_juan\", \"res_pedro\", \"res_leo\"]\n} html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":32,"title":31,"titles":192,"content":193,"level":80},[],"Servicios, requisitos con varios recursos, horarios y cómo se calculan los slots.",{"id":195,"title":196,"titles":197,"content":198,"level":86},"\u002Fdocs\u002Fconcepts\u002Fservices-and-availability#servicios","Servicios",[31],"Un servicio es lo que reserva el cliente. CampoSignificadoduration_minDuración del serviciostep_minGrilla de inicio (cada 15, 30 minutos…)buffer_before_min \u002F buffer_after_minTiempo de preparación o limpieza bloqueado alrededor de la reservaprice_cents, currencyPrecio que informa el bot (siempre desde la base de datos)deposit_centsSeña por Pix necesaria para confirmarlead_time_minAnticipación mínima (por ejemplo 60 minutos)max_advance_daysCon cuánta anticipación máxima se puede reservarwindow_modeEl slot es una ventana fija (entregas: 14:00–16:00)fulfillment_modescheduled (default, con horario) o queue (despacho por cola, sin horario fijo)intake_formJSON Schema de datos extra a pedir (obra social, dirección…)requirementsQué recursos necesita el servicio al mismo tiempo",{"id":200,"title":201,"titles":202,"content":203,"level":86},"\u002Fdocs\u002Fconcepts\u002Fservices-and-availability#ventana-o-despacho-dos-modos-de-cumplimiento","Ventana o despacho: dos modos de cumplimiento",[31],"Cada servicio define cómo se cumple lo reservado (fulfillment_mode): scheduled (el default): la reserva ocupa un rango de tiempo conocido — un horario fijo o, con window_mode, una ventana (entregas de 14:00 a 16:00, con cupo por zona).queue (despacho, para agua, gas o delivery on-demand): la reserva no tiene horario fijo — entra en la cola de pendientes de un repartidor. El motor la asigna al repartidor con menos tareas activas y cada repartidor tiene un cupo de tareas activas a la vez, garantizado en la base de datos. La cola usa los mismos estados que una reserva: pendiente → aceptada → iniciada → finalizada (o fallida). Así, un mismo negocio de entregas puede ofrecer ventanas programadas, despacho inmediato o ambos.",{"id":205,"title":206,"titles":207,"content":208,"level":86},"\u002Fdocs\u002Fconcepts\u002Fservices-and-availability#requisitos","Requisitos",[31],"Un requisito dice: de este grupo, necesito tantas unidades. Un servicio puede tener varios; un slot existe solo si todos están libres. {\n  \"name\": \"Depilación láser\",\n  \"duration_min\": 45,\n  \"buffer_after_min\": 10,\n  \"requirements\": [\n    { \"resource_group_id\": \"grp_professionals\", \"units\": 1 },\n    { \"resource_group_id\": \"grp_lasers\", \"units\": 1 },\n    { \"resource_group_id\": \"grp_rooms\", \"units\": 1 }\n  ]\n} Con dos profesionales y un solo láser, Wagend nunca ofrece dos sesiones de láser superpuestas. Opciones avanzadas: units: \"party_size\" — consume tantas unidades como personas (restaurantes, clases).offset_min \u002F duration_min en un requisito — usa un recurso solo en parte del servicio (por ejemplo el lavacabezas en los últimos 15 minutos de un color).",{"id":210,"title":211,"titles":212,"content":213,"level":86},"\u002Fdocs\u002Fconcepts\u002Fservices-and-availability#horarios","Horarios",[31],"Cada recurso tiene un horario: reglas semanales en la zona horaria del recurso más excepciones para fechas puntuales (cerrado u horario especial). {\n  \"tz\": \"America\u002FArgentina\u002FBuenos_Aires\",\n  \"weekly\": { \"tue\": [[\"09:00\", \"19:00\"]], \"sat\": [[\"08:00\", \"12:00\"], [\"13:00\", \"16:00\"]] },\n  \"overrides\": [{ \"date\": \"2026-12-24\", \"ranges\": [[\"09:00\", \"13:00\"]] }, { \"date\": \"2026-12-25\", \"closed\": true }]\n}",{"id":215,"title":216,"titles":217,"content":218,"level":86},"\u002Fdocs\u002Fconcepts\u002Fservices-and-availability#cómo-se-calculan-los-slots","Cómo se calculan los slots",[31],"Expandir los horarios para el rango pedido, en la zona horaria de cada recurso.Restar reservas y pre-reservas activas (más los buffers) y bloqueos manuales.Cortar en slots candidatos según la duración y la grilla del servicio.Aplicar anticipación mínima y máxima, reglas de corte y tamaño del grupo.Cruzar todos los requisitos, probando los miembros de cada grupo según la estrategia de asignación.Devolver una lista corta y ordenada. Cuando no hay nada disponible, la respuesta trae unavailable_reason (closed, no_staff, no_equipment, no_space, lead_time, max_advance, cutoff, full) para que el bot explique por qué. html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":36,"title":35,"titles":220,"content":221,"level":80},[],"Estados de la reserva, la pre-reserva de 10 minutos, señas por Pix y la garantía de cero sobreturnos.",{"id":223,"title":224,"titles":225,"content":226,"level":86},"\u002Fdocs\u002Fconcepts\u002Fbookings-and-holds#por-qué-una-pre-reserva","Por qué una pre-reserva",[35],"Una conversación por WhatsApp puede tardar minutos: el cliente elige horario, después le pedís el nombre, quizás una seña. Mientras tanto otros clientes, la página de reserva y la API compiten por el mismo horario. La pre-reserva (hold) guarda el horario por 10 minutos (15 cuando hay seña por Pix) y vence sola.",{"id":228,"title":229,"titles":230,"content":231,"level":86},"\u002Fdocs\u002Fconcepts\u002Fbookings-and-holds#estados","Estados",[35],"EstadoSignificadoOcupa el horarioheldPre-reserva temporalSípending_paymentEsperando la seña por PixSíconfirmedReservadoSíchecked_inEl cliente llegóSícompletedAtendidoNocancelledCancelado (o reemplazado por una reprogramación)Nono_showAusencia marcada por una personaNoexpiredPre-reserva no confirmada a tiempoNo held ──confirm──► confirmed ──check-in──► checked_in ──► completed\n │  └─(seña)──► pending_payment ──pagada──► confirmed\n └─vence──► expired          confirmed ──cancela──► cancelled",{"id":233,"title":234,"titles":235,"content":236,"level":86},"\u002Fdocs\u002Fconcepts\u002Fbookings-and-holds#garantías","Garantías",[35],"Cada recurso que ocupa una reserva se guarda como una asignación con un rango de tiempo.Para recursos exclusive, PostgreSQL rechaza asignaciones superpuestas con una constraint de exclusión.Para recursos pooled, los contadores de capacidad nunca superan el límite (chequeo atómico).Una reserva con varios recursos se escribe en una sola transacción: si cualquier recurso choca, no se reserva nada.POST \u002Fholds y POST \u002Fholds\u002F{id}\u002Fconfirm exigen Idempotency-Key: reintentar un request nunca crea una segunda reserva. Si el horario se ocupó mientras el cliente decidía, recibís 409 con code: \"slot_taken\" y una lista de alternatives.",{"id":238,"title":239,"titles":240,"content":241,"level":86},"\u002Fdocs\u002Fconcepts\u002Fbookings-and-holds#reprogramar-y-cancelar","Reprogramar y cancelar",[35],"Reprogramar crea una reserva nueva y cancela la anterior (vinculadas por rescheduled_to). Los recordatorios se mueven solos.Cancelar libera el horario y cancela los recordatorios pendientes.Ausencia nunca es automática: el sistema la sugiere, una persona la confirma.",{"id":243,"title":244,"titles":245,"content":246,"level":86},"\u002Fdocs\u002Fconcepts\u002Fbookings-and-holds#señas","Señas",[35],"Si el servicio tiene deposit_cents, confirmar la pre-reserva devuelve pending_payment con un código Pix. Cuando el proveedor de pagos avisa, la reserva pasa a confirmed y se emite payment.paid. Un pago que llega después de que venció la pre-reserva queda marcado para reembolso.",{"id":40,"title":39,"titles":248,"content":249,"level":80},[],"Recordatorios, confirmaciones y avisos como reglas \"cuando pase esto, hacé aquello\". Las automatizaciones son reglas con un disparador, condiciones opcionales y una acción. Cada modelo de negocio trae valores por defecto que podés editar.",{"id":251,"title":252,"titles":253,"content":254,"level":86},"\u002Fdocs\u002Fconcepts\u002Fautomations#disparadores","Disparadores",[39],"DisparadorEjemplobooking.confirmedEnviar el resumen de la reservabooking.starts_in(Δ)24 h antes: pedir confirmación SÍ\u002FNObooking.ended(+Δ)1 h después: sugerir revisar asistenciabooking.cancelled, booking.rescheduledAvisar al equipohold.expiredRetomar contacto con el clientepayment.paidConfirmar y agradecerdaily_at(hh:mm)Enviar la agenda del día al equipo",{"id":256,"title":257,"titles":258,"content":259,"level":86},"\u002Fdocs\u002Fconcepts\u002Fautomations#condiciones","Condiciones",[39],"Servicio, recurso, tamaño de grupo mayor a N, canal, etiqueta del cliente, cantidad de ausencias anteriores.",{"id":261,"title":262,"titles":263,"content":264,"level":86},"\u002Fdocs\u002Fconcepts\u002Fautomations#acciones","Acciones",[39],"Enviar un mensaje o plantilla al cliente, avisar al equipo, pedir confirmación SÍ\u002FNO, cambiar el estado de la reserva, crear una tarea interna, llamar un webhook.",{"id":266,"title":267,"titles":268,"content":269,"level":86},"\u002Fdocs\u002Fconcepts\u002Fautomations#valores-por-defecto-por-negocio","Valores por defecto por negocio",[39],"ModeloAutomatizacionesBarbería, estética, clínicaRecordatorio 24 h antes con SÍ\u002FNO · aviso al profesional 2 h antes · revisar asistencia 1 h después · agenda del día a las 7:30RestauranteRecordatorio el día anterior · confirmación obligatoria para grupos de más de 6 · agenda del díaEntregas\"En camino\" 1 h antes · confirmación de recepción",{"id":271,"title":272,"titles":273,"content":274,"level":86},"\u002Fdocs\u002Fconcepts\u002Fautomations#garantías-de-ejecución","Garantías de ejecución",[39],"Los jobs se crean en la misma transacción que el evento de la reserva, así que nunca se pierden.Antes de ejecutarse, el job vuelve a revisar la reserva. Si se reprogramó o canceló, el job viejo se descarta.Cada job corre una vez (clave de idempotencia) y reintenta con espera creciente; después de 5 fallos aparece en el panel.Horario de silencio: no se envía nada a clientes entre las 21:00 y las 08:00 de su zona horaria.Las respuestas SÍ\u002FNO se interpretan sin IA (sim, sí, ok, 👍, não…): instantáneo y gratis.",{"id":45,"title":44,"titles":276,"content":277,"level":80},[],"Conectar un número, qué puede y qué no puede hacer el asistente, acciones del equipo y derivación a una persona.",{"id":279,"title":280,"titles":281,"content":282,"level":86},"\u002Fdocs\u002Fwhatsapp-and-ai#conectar-un-número","Conectar un número",[44],"En el panel, andá a Canales → WhatsApp → Conectar y escaneá el QR con el celular del negocio, como en WhatsApp Web. Un workspace puede conectar varios números (uno por sede, por ejemplo). Usá un número dedicado al negocio. Para tilde verde o envíos masivos se va a ofrecer una conexión por la plataforma oficial de WhatsApp Business como opción.",{"id":284,"title":285,"titles":286,"content":287,"level":86},"\u002Fdocs\u002Fwhatsapp-and-ai#clientes-y-equipo","Clientes y equipo",[44],"El rol se decide en el código, nunca por lo que alguien dice en el chat: Un número vinculado a alguien del equipo (verificado con un código de un solo uso) recibe herramientas de equipo.Todos los demás reciben herramientas de cliente. Herramientas de clienteHerramientas de equipo (extra)list_services, find_slots, hold_slot, confirm_hold, my_bookings, cancel_my_booking, reschedule_my_booking, get_business_info, search_knowledge, handoff_to_humanlist_today, block_time, mark_no_show, update_catalog_item — siempre con confirmación SÍ\u002FNO",{"id":289,"title":290,"titles":291,"content":292,"level":86},"\u002Fdocs\u002Fwhatsapp-and-ai#qué-puede-y-qué-no-puede-hacer-la-ia","Qué puede y qué no puede hacer la IA",[44],"PuedeNo puedeEntender texto, audio, errores de tipeo y cambios de ideaInventar horarios, precios o direccionesBuscar horarios y ofrecer 2–3 opcionesConfirmar una reserva sin el motorPre-reservar y pedir los datos que faltanUsar herramientas de administrador con un clienteResponder dudas con la base de conocimiento del negocioSeguir instrucciones escondidas en mensajes o archivosPasar la conversación a una personaVer datos de otro negocio Los ids de workspace, los ids de cliente y los precios los inyecta el sistema; el modelo nunca los aporta.",{"id":294,"title":295,"titles":296,"content":297,"level":86},"\u002Fdocs\u002Fwhatsapp-and-ai#derivación-a-una-persona","Derivación a una persona",[44],"El equipo ve todas las conversaciones en vivo en el Inbox. Tomar el control pausa el bot en esa conversación; vuelve solo después de 15 minutos sin actividad del operador. El asistente también deriva cuando el cliente lo pide (\"quiero hablar con una persona\").",{"id":299,"title":300,"titles":301,"content":302,"level":86},"\u002Fdocs\u002Fwhatsapp-and-ai#audios-e-idiomas","Audios e idiomas",[44],"Los audios se transcriben antes de que la IA los lea, y la transcripción se ve en el inbox. El asistente responde en el idioma del cliente (portugués, español o inglés).",{"id":304,"title":305,"titles":306,"content":307,"level":86},"\u002Fdocs\u002Fwhatsapp-and-ai#modelos","Modelos",[44],"La capa de IA corre por OpenRouter con una clave y un límite de gasto propios por workspace. Por defecto usa un modelo rápido y barato; un modelo más potente se usa solo en los turnos que fallan la validación. Los proveedores se fijan entre los que no retienen datos.",{"id":54,"title":53,"titles":309,"content":310,"level":80},[],"Claves de API, scopes, modos de prueba y producción.",{"id":312,"title":313,"titles":314,"content":315,"level":86},"\u002Fdocs\u002Fapi\u002Fauthentication#claves-de-api","Claves de API",[53],"Enviá tu clave como bearer token: GET \u002Fv1\u002Fme HTTP\u002F1.1\nHost: api.wagend.app\nAuthorization: Bearer wg_live_3fa9c2_Lr8t... PrefijoModowg_live_Datos de producción, mensajes y pagos realeswg_test_Sandbox: datos aislados, sin envío de mensajes, pagos simulados Las claves pertenecen a un workspace. El workspace siempre se toma de la clave: no hay id de workspace en paths ni en bodies. Las claves se guardan hasheadas; la clave completa se muestra una sola vez, al crearla.",{"id":317,"title":318,"titles":319,"content":320,"level":86},"\u002Fdocs\u002Fapi\u002Fauthentication#scopes","Scopes",[53],"ScopePermiteslots:readGET \u002Fslotsbookings:readListar y leer reservasbookings:writePre-reservas, confirmar, cancelar, reprogramar, check-in, ausenciacustomers:readLeer clientesmessages:readLeer conversaciones y mensajesmessages:writeEnviar mensajes, derivarconfig:read \u002F config:writeServicios, recursos, grupos, horarios, automatizaciones, conocimientowebhooks:manageEndpoints de webhooks Un request sin el scope necesario devuelve 403 con code: \"insufficient_scope\".",{"id":322,"title":323,"titles":324,"content":325,"level":86},"\u002Fdocs\u002Fapi\u002Fauthentication#rotación","Rotación",[53],"Creá una clave nueva, desplegala y después revocá la vieja en Desarrolladores → Claves de API. Las claves revocadas devuelven 401 al instante. html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":58,"title":57,"titles":327,"content":328,"level":80},[],"Formatos, idempotencia, paginación, errores y límites de uso.",{"id":330,"title":331,"titles":332,"content":333,"level":86},"\u002Fdocs\u002Fapi\u002Fconventions#lo-básico","Lo básico",[57],"URL base https:\u002F\u002Fapi.wagend.app\u002Fv1. Los cambios incompatibles salen como una nueva versión.JSON de entrada y de salida (Content-Type: application\u002Fjson).Fechas en ISO 8601 con offset (2026-10-15T09:45:00-03:00). Se guardan en UTC.Montos en centavos enteros más currency (BRL, ARS, PYG, USD).Los IDs son strings opacos con prefijo (bkg_, svc_, res_, grp_, cus_).",{"id":335,"title":336,"titles":337,"content":338,"level":86},"\u002Fdocs\u002Fapi\u002Fconventions#idempotencia","Idempotencia",[57],"POST \u002Fholds, POST \u002Fholds\u002F{id}\u002Fconfirm, POST \u002Fbookings y POST \u002Fbookings\u002F{id}\u002Freschedule exigen el header Idempotency-Key (por ejemplo un UUID). Repetir un request con la misma clave dentro de 24 horas devuelve la respuesta original. Reusar la clave con otro body devuelve 422.",{"id":340,"title":341,"titles":342,"content":343,"level":86},"\u002Fdocs\u002Fapi\u002Fconventions#paginación","Paginación",[57],"Los endpoints de listado usan cursores: curl \"https:\u002F\u002Fapi.wagend.app\u002Fv1\u002Fbookings?limit=50\" -H \"Authorization: Bearer $WAGEND_KEY\"\n# → { \"data\": [...], \"next_cursor\": \"eyJpZCI6...\" }\ncurl \"https:\u002F\u002Fapi.wagend.app\u002Fv1\u002Fbookings?limit=50&cursor=eyJpZCI6...\" -H \"Authorization: Bearer $WAGEND_KEY\"",{"id":345,"title":346,"titles":347,"content":348,"level":86},"\u002Fdocs\u002Fapi\u002Fconventions#errores","Errores",[57],"Los errores siguen la RFC 9457 (application\u002Fproblem+json): {\n  \"type\": \"https:\u002F\u002Fwagend.app\u002Fdocs\u002Fapi\u002Fconventions#errors\",\n  \"title\": \"Slot no longer available\",\n  \"status\": 409,\n  \"code\": \"slot_taken\",\n  \"alternatives\": [{ \"start\": \"2026-10-15T10:15:00-03:00\", \"end\": \"2026-10-15T10:45:00-03:00\" }]\n} codeEstadoCuándoinvalid_request400 \u002F 422Falló la validaciónunauthorized401Clave ausente, inválida o revocadainsufficient_scope403La clave no tiene el scopenot_found404Id desconocido (o de otro workspace)slot_taken409Otra persona tomó el horarioinvalid_transition409Por ejemplo confirmar una reserva canceladahold_expired410La pre-reserva venció antes de confirmaridempotency_mismatch422Misma clave, distinto bodyrate_limited429Demasiados requests",{"id":350,"title":351,"titles":352,"content":353,"level":86},"\u002Fdocs\u002Fapi\u002Fconventions#límites-de-uso","Límites de uso",[57],"Los límites aplican por clave y por workspace. Cada respuesta trae RateLimit-Limit, RateLimit-Remaining y RateLimit-Reset. Ante un 429, esperá Retry-After segundos. html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":62,"title":61,"titles":355,"content":356,"level":80},[],"Todos los endpoints de la v1 de un vistazo, con los formatos más comunes de request y respuesta. El contrato legible por máquina está en el repositorio, en packages\u002Fopenapi\u002Fopenapi.yaml (OpenAPI 3.1).",{"id":358,"title":359,"titles":360,"content":361,"level":86},"\u002Fdocs\u002Fapi\u002Fendpoints#agenda","Agenda",[61],"MétodoPathScopeDescripciónGET\u002Fslotsslots:readHorarios disponibles para un servicioPOST\u002Fholdsbookings:writeCrear pre-reserva de 10 minutosPOST\u002Fholds\u002F{id}\u002Fconfirmbookings:writeConfirmar (o pending_payment si pide seña)DELETE\u002Fholds\u002F{id}bookings:writeLiberar pre-reservaGET\u002Fbookingsbookings:readListar (filtros: from, to, status, resource_id, customer_id)POST\u002Fbookingsbookings:writePre-reserva + confirmación en una llamadaGET \u002F PATCH\u002Fbookings\u002F{id}bookings:read \u002F writeLeer, actualizar notas o datos de intakePOST\u002Fbookings\u002F{id}\u002Fcancelbookings:writeCancelarPOST\u002Fbookings\u002F{id}\u002Freschedulebookings:writeMover a un nuevo inicioPOST\u002Fbookings\u002F{id}\u002Fcheck-inbookings:writeMarcar llegadaPOST\u002Fbookings\u002F{id}\u002Fno-showbookings:writeMarcar ausencia",{"id":363,"title":364,"titles":365,"content":366,"level":86},"\u002Fdocs\u002Fapi\u002Fendpoints#catálogo","Catálogo",[61],"MétodoPathScopeGET \u002F POST\u002Fservicesconfig:read \u002F config:writeGET \u002F POST\u002Fresourcesconfig:read \u002F config:writeGET\u002Fresource-groupsconfig:readPOST\u002Fschedules\u002F{id}\u002Foverridesconfig:write",{"id":368,"title":369,"titles":370,"content":371,"level":86},"\u002Fdocs\u002Fapi\u002Fendpoints#clientes-y-conversaciones","Clientes y conversaciones",[61],"MétodoPathScopeGET\u002Fcustomerscustomers:readDELETE\u002Fcustomers\u002F{id}customers:read + rol owner (anonimiza, LGPD)GET\u002Fconversationsmessages:readGET \u002F POST\u002Fconversations\u002F{id}\u002Fmessagesmessages:read \u002F messages:writePOST\u002Fconversations\u002F{id}\u002Fhandoffmessages:write",{"id":373,"title":374,"titles":375,"content":376,"level":86},"\u002Fdocs\u002Fapi\u002Fendpoints#plataforma","Plataforma",[61],"MétodoPathScopeGET\u002FmecualquieraGET \u002F POST\u002Fwebhook-endpointswebhooks:manageGET\u002Feventsbookings:read",{"id":378,"title":379,"titles":380,"content":381,"level":86},"\u002Fdocs\u002Fapi\u002Fendpoints#ejemplo-slots","Ejemplo: slots",[61],"GET \u002Fv1\u002Fslots?service_id=svc_laser&from=2026-10-20T09:00:00-03:00&to=2026-10-20T20:00:00-03:00&around=2026-10-20T18:00:00-03:00 {\n  \"data\": [\n    { \"start\": \"2026-10-20T17:45:00-03:00\", \"end\": \"2026-10-20T18:30:00-03:00\", \"resource_ids\": [\"res_ana\", \"res_laser1\", \"res_room2\"] },\n    { \"start\": \"2026-10-20T18:45:00-03:00\", \"end\": \"2026-10-20T19:30:00-03:00\", \"resource_ids\": [\"res_carla\", \"res_laser1\", \"res_room1\"] }\n  ],\n  \"unavailable_reason\": null\n}",{"id":383,"title":384,"titles":385,"content":386,"level":86},"\u002Fdocs\u002Fapi\u002Fendpoints#ejemplo-objeto-de-reserva","Ejemplo: objeto de reserva",[61],"{\n  \"id\": \"bkg_7Qx1\",\n  \"status\": \"confirmed\",\n  \"service_id\": \"svc_laser\",\n  \"start\": \"2026-10-20T17:45:00-03:00\",\n  \"end\": \"2026-10-20T18:30:00-03:00\",\n  \"party_size\": 1,\n  \"customer\": { \"id\": \"cus_31\", \"name\": \"Marina\", \"phone\": \"+5521988887777\", \"locale\": \"pt\" },\n  \"allocations\": [\n    { \"resource_id\": \"res_ana\", \"start\": \"2026-10-20T17:45:00-03:00\", \"end\": \"2026-10-20T18:40:00-03:00\", \"units\": 1 },\n    { \"resource_id\": \"res_laser1\", \"start\": \"2026-10-20T17:45:00-03:00\", \"end\": \"2026-10-20T18:40:00-03:00\", \"units\": 1 },\n    { \"resource_id\": \"res_room2\", \"start\": \"2026-10-20T17:45:00-03:00\", \"end\": \"2026-10-20T18:40:00-03:00\", \"units\": 1 }\n  ],\n  \"payment\": { \"status\": \"paid\", \"amount_cents\": 5000 },\n  \"source\": \"whatsapp\",\n  \"created_at\": \"2026-10-19T11:02:13-03:00\"\n} Las asignaciones incluyen el buffer (acá, 10 minutos después del servicio). html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"id":66,"title":65,"titles":388,"content":389,"level":80},[],"Eventos firmados de reservas, pagos y conversaciones, con reintentos automáticos.",{"id":391,"title":392,"titles":393,"content":394,"level":86},"\u002Fdocs\u002Fapi\u002Fwebhooks#eventos","Eventos",[65],"EventoCuándobooking.createdSe creó una pre-reserva o reservabooking.confirmedSe confirmó una reserva (también después del pago)booking.cancelledCancelada o vencidabooking.rescheduledSe movió de horario (el payload trae el id viejo y el nuevo)booking.no_showMarcada como ausenciapayment.paidSe recibió una seña por Pixmessage.receivedLlegó un mensaje de un clienteconversation.handoffUna conversación se derivó a una persona Creá endpoints en Desarrolladores → Webhooks o con POST \u002Fv1\u002Fwebhook-endpoints. El secreto de firma se muestra una sola vez.",{"id":396,"title":397,"titles":398,"content":399,"level":86},"\u002Fdocs\u002Fapi\u002Fwebhooks#payload","Payload",[65],"{\n  \"id\": \"evt_01J9ZK3T6\",\n  \"type\": \"booking.confirmed\",\n  \"created_at\": \"2026-10-14T15:21:07-03:00\",\n  \"workspace_id\": \"ws_9f2c\",\n  \"data\": { \"booking\": { \"id\": \"bkg_7Qx1\", \"status\": \"confirmed\", \"start\": \"2026-10-15T09:45:00-03:00\" } }\n} La entrega es al menos una vez: usá el id para ignorar duplicados.",{"id":401,"title":402,"titles":403,"content":404,"level":86},"\u002Fdocs\u002Fapi\u002Fwebhooks#verificar-la-firma","Verificar la firma",[65],"Cada request trae el header: Wagend-Signature: t=1791040867,v1=5c2b9f...e81 v1 es HMAC-SHA256(secreto, t + \".\" + body_crudo) en hexadecimal. Rechazá requests de más de 5 minutos. import crypto from 'node:crypto'\n\nexport function verifyWagend(rawBody: string, header: string, secret: string) {\n  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=') as [string, string]))\n  const age = Math.abs(Date.now() \u002F 1000 - Number(parts.t))\n  if (!parts.t || !parts.v1 || age > 300) return false\n  const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex')\n  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))\n} import hashlib, hmac, time\n\ndef verify_wagend(raw_body: bytes, header: str, secret: str) -> bool:\n    parts = dict(p.split(\"=\", 1) for p in header.split(\",\"))\n    if abs(time.time() - int(parts.get(\"t\", \"0\"))) > 300:\n        return False\n    signed = f\"{parts['t']}.\".encode() + raw_body\n    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(expected, parts.get(\"v1\", \"\"))",{"id":406,"title":407,"titles":408,"content":409,"level":86},"\u002Fdocs\u002Fapi\u002Fwebhooks#reintentos","Reintentos",[65],"Respondé con cualquier 2xx en menos de 10 segundos. Si no, reintentamos a los 1 min, 5 min, 30 min, 2 h y 12 h. Cada intento se ve en el log de entregas, donde también podés reenviar a mano. html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"id":70,"title":69,"titles":411,"content":412,"level":80},[],"Dejá que Claude, ChatGPT o tu propio agente busque horarios, reserve y opere un negocio por Model Context Protocol. Wagend expone un servidor MCP para que los agentes de IA usen el mismo motor que el bot de WhatsApp: mismas pre-reservas, misma validación, mismo registro de auditoría.",{"id":414,"title":415,"titles":416,"content":417,"level":86},"\u002Fdocs\u002Fmcp#conectar","Conectar",[69],"URL: https:\u002F\u002Fmcp.wagend.app (Streamable HTTP)Autenticación: una clave de API en el header Authorization. OAuth llega más adelante. Ejemplo de configuración del cliente: {\n  \"mcpServers\": {\n    \"wagend\": {\n      \"type\": \"http\",\n      \"url\": \"https:\u002F\u002Fmcp.wagend.app\",\n      \"headers\": { \"Authorization\": \"Bearer wg_live_...\" }\n    }\n  }\n} Usá una clave wg_test_ mientras probás.",{"id":419,"title":420,"titles":421,"content":422,"level":86},"\u002Fdocs\u002Fmcp#conjuntos-de-herramientas","Conjuntos de herramientas",[69],"Las herramientas que ve el agente dependen de los scopes de la clave. Reservas (slots:read, bookings:write) — para asistentes que reservan en nombre de un cliente: HerramientaHacelist_servicesServicios con duración y preciofind_slotsHorarios disponibles para un servicio y un rango de fechashold_slotPre-reserva de 10 minutosconfirm_bookingConfirma una pre-reserva con los datos del clientecancel_booking, reschedule_booking, get_bookingGestiona una reserva existente Administración (config:write) — para que el dueño opere el negocio desde su asistente de IA: HerramientaHacelist_resources, list_todayEquipo, salas, máquinas y agenda del díablock_timeBloquea un recurso (vacaciones, mantenimiento)create_service, update_scheduleCambia el catálogo y los horariosget_statsReservas, ocupación y ausencias",{"id":424,"title":425,"titles":426,"content":427,"level":86},"\u002Fdocs\u002Fmcp#ejemplos-de-pedidos","Ejemplos de pedidos",[69],"\"Reservame un corte con Juan mañana a la mañana, a nombre de Carlos.\"\"Bloqueá la máquina de láser el lunes que viene por mantenimiento y decime qué reservas quedan afectadas.\"\"¿Cuántas ausencias tuvimos este mes?\"",{"id":429,"title":430,"titles":431,"content":432,"level":86},"\u002Fdocs\u002Fmcp#docs-para-llms","Docs para LLMs",[69],"\u002Fllms.txt lista todas las páginas de la documentación.\u002Fllms-full.txt contiene la documentación completa en inglés, en Markdown. html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":74,"title":73,"titles":434,"content":435,"level":80},[],"Cómo modelar una barbería, un centro de estética, un consultorio, un restaurante y entregas. Cada receta corresponde a un modelo listo que elegís en el onboarding.",{"id":437,"title":438,"titles":439,"content":440,"level":86},"\u002Fdocs\u002Frecipes#barbería","Barbería",[73],"Cada barbero es un recurso staff exclusive con su propio horario. Un grupo, customer_choice con round_robin como alternativa. {\n  \"name\": \"Corte\",\n  \"duration_min\": 30,\n  \"step_min\": 15,\n  \"buffer_after_min\": 5,\n  \"price_cents\": 5000,\n  \"requirements\": [{ \"resource_group_id\": \"grp_barbers\", \"units\": 1 }]\n}",{"id":442,"title":443,"titles":444,"content":445,"level":86},"\u002Fdocs\u002Frecipes#estética","Estética",[73],"Profesionales, máquinas y cabinas son recursos separados. El servicio pide los tres al mismo tiempo, así que el más escaso (casi siempre la máquina) limita la agenda. {\n  \"name\": \"Depilación láser\",\n  \"duration_min\": 45,\n  \"buffer_after_min\": 10,\n  \"price_cents\": 18000,\n  \"deposit_cents\": 5000,\n  \"requirements\": [\n    { \"resource_group_id\": \"grp_professionals\", \"units\": 1 },\n    { \"resource_group_id\": \"grp_lasers\", \"units\": 1 },\n    { \"resource_group_id\": \"grp_rooms\", \"units\": 1 }\n  ]\n}",{"id":447,"title":448,"titles":449,"content":450,"level":86},"\u002Fdocs\u002Frecipes#clínica","Clínica",[73],"Médico más consultorio, por sede (un workspace por sede). Los datos de salud son sensibles: el asistente nunca pregunta síntomas y el formulario pide solo lo necesario para reservar. {\n  \"name\": \"Consulta\",\n  \"duration_min\": 30,\n  \"buffer_after_min\": 10,\n  \"intake_form\": { \"type\": \"object\", \"properties\": { \"obra_social\": { \"type\": \"string\" } } },\n  \"requirements\": [\n    { \"resource_group_id\": \"grp_doctors\", \"units\": 1 },\n    { \"resource_group_id\": \"grp_offices\", \"units\": 1 }\n  ]\n}",{"id":452,"title":453,"titles":454,"content":455,"level":86},"\u002Fdocs\u002Frecipes#restaurante","Restaurante",[73],"El salón es un recurso pooled con 40 unidades (cubiertos) por turno. La duración crece con el tamaño del grupo, y los grupos grandes pagan seña. {\n  \"name\": \"Mesa\",\n  \"step_min\": 30,\n  \"duration_by_units\": [\n    { \"max_units\": 2, \"duration_min\": 90 },\n    { \"max_units\": 4, \"duration_min\": 120 },\n    { \"max_units\": 20, \"duration_min\": 150 }\n  ],\n  \"requirements\": [{ \"resource_group_id\": \"grp_dining_room\", \"units\": \"party_size\" }]\n} El modo mesas (mesas puntuales con tamaño mínimo y máximo, y combinaciones) está en el roadmap.",{"id":457,"title":458,"titles":459,"content":460,"level":86},"\u002Fdocs\u002Frecipes#entregas","Entregas",[73],"Cada zona es un recurso pooled con capacidad por ventana de 2 horas. La zona se elige por código postal, y una regla de corte cierra las ventanas del mismo día al mediodía. {\n  \"name\": \"Entrega\",\n  \"window_mode\": true,\n  \"duration_min\": 120,\n  \"cutoff_rule\": { \"same_day_until\": \"12:00\" },\n  \"intake_form\": { \"type\": \"object\", \"required\": [\"address\", \"postal_code\"] },\n  \"requirements\": [{ \"resource_group_id\": \"grp_zones\", \"units\": 1, \"select_by\": \"postal_code\" }]\n} html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":462,"title":65,"badge":463,"body":464,"description":389,"extension":1292,"meta":1293,"navigation":773,"path":66,"rawbody":1294,"seo":1295,"stem":67,"__hash__":1296},"docs_es\u002Fdocs\u002F6.api\u002F4.webhooks.md","Borrador",{"type":465,"value":466,"toc":1286},"minimark",[467,471,572,585,588,712,723,726,729,737,747,1065,1272,1275,1282],[468,469,392],"h2",{"id":470},"eventos",[472,473,474,487],"table",{},[475,476,477],"thead",{},[478,479,480,484],"tr",{},[481,482,483],"th",{},"Evento",[481,485,486],{},"Cuándo",[488,489,490,502,512,522,532,542,552,562],"tbody",{},[478,491,492,499],{},[493,494,495],"td",{},[496,497,498],"code",{},"booking.created",[493,500,501],{},"Se creó una pre-reserva o reserva",[478,503,504,509],{},[493,505,506],{},[496,507,508],{},"booking.confirmed",[493,510,511],{},"Se confirmó una reserva (también después del pago)",[478,513,514,519],{},[493,515,516],{},[496,517,518],{},"booking.cancelled",[493,520,521],{},"Cancelada o vencida",[478,523,524,529],{},[493,525,526],{},[496,527,528],{},"booking.rescheduled",[493,530,531],{},"Se movió de horario (el payload trae el id viejo y el nuevo)",[478,533,534,539],{},[493,535,536],{},[496,537,538],{},"booking.no_show",[493,540,541],{},"Marcada como ausencia",[478,543,544,549],{},[493,545,546],{},[496,547,548],{},"payment.paid",[493,550,551],{},"Se recibió una seña por Pix",[478,553,554,559],{},[493,555,556],{},[496,557,558],{},"message.received",[493,560,561],{},"Llegó un mensaje de un cliente",[478,563,564,569],{},[493,565,566],{},[496,567,568],{},"conversation.handoff",[493,570,571],{},"Una conversación se derivó a una persona",[573,574,575,576,580,581,584],"p",{},"Creá endpoints en ",[577,578,579],"strong",{},"Desarrolladores → Webhooks"," o con ",[496,582,583],{},"POST \u002Fv1\u002Fwebhook-endpoints",". El secreto de firma se muestra una sola vez.",[468,586,397],{"id":587},"payload",[589,590,595],"pre",{"className":591,"code":592,"language":593,"meta":594,"style":594},"language-json shiki shiki-themes github-light github-dark","{\n  \"id\": \"evt_01J9ZK3T6\",\n  \"type\": \"booking.confirmed\",\n  \"created_at\": \"2026-10-14T15:21:07-03:00\",\n  \"workspace_id\": \"ws_9f2c\",\n  \"data\": { \"booking\": { \"id\": \"bkg_7Qx1\", \"status\": \"confirmed\", \"start\": \"2026-10-15T09:45:00-03:00\" } }\n}\n","json","",[496,596,597,605,621,634,647,660,706],{"__ignoreMap":594},[598,599,601],"span",{"class":600,"line":80},"line",[598,602,604],{"class":603},"sVt8B","{\n",[598,606,607,611,614,618],{"class":600,"line":86},[598,608,610],{"class":609},"sj4cs","  \"id\"",[598,612,613],{"class":603},": ",[598,615,617],{"class":616},"sZZnC","\"evt_01J9ZK3T6\"",[598,619,620],{"class":603},",\n",[598,622,624,627,629,632],{"class":600,"line":623},3,[598,625,626],{"class":609},"  \"type\"",[598,628,613],{"class":603},[598,630,631],{"class":616},"\"booking.confirmed\"",[598,633,620],{"class":603},[598,635,637,640,642,645],{"class":600,"line":636},4,[598,638,639],{"class":609},"  \"created_at\"",[598,641,613],{"class":603},[598,643,644],{"class":616},"\"2026-10-14T15:21:07-03:00\"",[598,646,620],{"class":603},[598,648,650,653,655,658],{"class":600,"line":649},5,[598,651,652],{"class":609},"  \"workspace_id\"",[598,654,613],{"class":603},[598,656,657],{"class":616},"\"ws_9f2c\"",[598,659,620],{"class":603},[598,661,663,666,669,672,674,677,679,682,685,688,690,693,695,698,700,703],{"class":600,"line":662},6,[598,664,665],{"class":609},"  \"data\"",[598,667,668],{"class":603},": { ",[598,670,671],{"class":609},"\"booking\"",[598,673,668],{"class":603},[598,675,676],{"class":609},"\"id\"",[598,678,613],{"class":603},[598,680,681],{"class":616},"\"bkg_7Qx1\"",[598,683,684],{"class":603},", ",[598,686,687],{"class":609},"\"status\"",[598,689,613],{"class":603},[598,691,692],{"class":616},"\"confirmed\"",[598,694,684],{"class":603},[598,696,697],{"class":609},"\"start\"",[598,699,613],{"class":603},[598,701,702],{"class":616},"\"2026-10-15T09:45:00-03:00\"",[598,704,705],{"class":603}," } }\n",[598,707,709],{"class":600,"line":708},7,[598,710,711],{"class":603},"}\n",[573,713,714,715,718,719,722],{},"La entrega es ",[577,716,717],{},"al menos una vez",": usá el ",[496,720,721],{},"id"," para ignorar duplicados.",[468,724,402],{"id":725},"verificar-la-firma",[573,727,728],{},"Cada request trae el header:",[589,730,735],{"className":731,"code":733,"language":734,"meta":594},[732],"language-text","Wagend-Signature: t=1791040867,v1=5c2b9f...e81\n","text",[496,736,733],{"__ignoreMap":594},[573,738,739,742,743,746],{},[496,740,741],{},"v1"," es ",[496,744,745],{},"HMAC-SHA256(secreto, t + \".\" + body_crudo)"," en hexadecimal. Rechazá requests de más de 5 minutos.",[589,748,752],{"className":749,"code":750,"language":751,"meta":594,"style":594},"language-ts shiki shiki-themes github-light github-dark","import crypto from 'node:crypto'\n\nexport function verifyWagend(rawBody: string, header: string, secret: string) {\n  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=') as [string, string]))\n  const age = Math.abs(Date.now() \u002F 1000 - Number(parts.t))\n  if (!parts.t || !parts.v1 || age > 300) return false\n  const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex')\n  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))\n}\n","ts",[496,753,754,769,775,821,894,933,975,1036,1060],{"__ignoreMap":594},[598,755,756,760,763,766],{"class":600,"line":80},[598,757,759],{"class":758},"szBVR","import",[598,761,762],{"class":603}," crypto ",[598,764,765],{"class":758},"from",[598,767,768],{"class":616}," 'node:crypto'\n",[598,770,771],{"class":600,"line":86},[598,772,774],{"emptyLinePlaceholder":773},true,"\n",[598,776,777,780,783,787,790,794,797,800,802,805,807,809,811,814,816,818],{"class":600,"line":623},[598,778,779],{"class":758},"export",[598,781,782],{"class":758}," function",[598,784,786],{"class":785},"sScJk"," verifyWagend",[598,788,789],{"class":603},"(",[598,791,793],{"class":792},"s4XuR","rawBody",[598,795,796],{"class":758},":",[598,798,799],{"class":609}," string",[598,801,684],{"class":603},[598,803,804],{"class":792},"header",[598,806,796],{"class":758},[598,808,799],{"class":609},[598,810,684],{"class":603},[598,812,813],{"class":792},"secret",[598,815,796],{"class":758},[598,817,799],{"class":609},[598,819,820],{"class":603},") {\n",[598,822,823,826,829,832,835,838,841,844,846,849,852,855,858,860,863,866,869,871,873,876,878,881,884,887,889,891],{"class":600,"line":636},[598,824,825],{"class":758},"  const",[598,827,828],{"class":609}," parts",[598,830,831],{"class":758}," =",[598,833,834],{"class":603}," Object.",[598,836,837],{"class":785},"fromEntries",[598,839,840],{"class":603},"(header.",[598,842,843],{"class":785},"split",[598,845,789],{"class":603},[598,847,848],{"class":616},"','",[598,850,851],{"class":603},").",[598,853,854],{"class":785},"map",[598,856,857],{"class":603},"((",[598,859,573],{"class":792},[598,861,862],{"class":603},") ",[598,864,865],{"class":758},"=>",[598,867,868],{"class":603}," p.",[598,870,843],{"class":785},[598,872,789],{"class":603},[598,874,875],{"class":616},"'='",[598,877,862],{"class":603},[598,879,880],{"class":758},"as",[598,882,883],{"class":603}," [",[598,885,886],{"class":609},"string",[598,888,684],{"class":603},[598,890,886],{"class":609},[598,892,893],{"class":603},"]))\n",[598,895,896,898,901,903,906,909,912,915,918,921,924,927,930],{"class":600,"line":649},[598,897,825],{"class":758},[598,899,900],{"class":609}," age",[598,902,831],{"class":758},[598,904,905],{"class":603}," Math.",[598,907,908],{"class":785},"abs",[598,910,911],{"class":603},"(Date.",[598,913,914],{"class":785},"now",[598,916,917],{"class":603},"() ",[598,919,920],{"class":758},"\u002F",[598,922,923],{"class":609}," 1000",[598,925,926],{"class":758}," -",[598,928,929],{"class":785}," Number",[598,931,932],{"class":603},"(parts.t))\n",[598,934,935,938,941,944,947,950,953,956,958,961,964,967,969,972],{"class":600,"line":662},[598,936,937],{"class":758},"  if",[598,939,940],{"class":603}," (",[598,942,943],{"class":758},"!",[598,945,946],{"class":603},"parts.t ",[598,948,949],{"class":758},"||",[598,951,952],{"class":758}," !",[598,954,955],{"class":603},"parts.v1 ",[598,957,949],{"class":758},[598,959,960],{"class":603}," age ",[598,962,963],{"class":758},">",[598,965,966],{"class":609}," 300",[598,968,862],{"class":603},[598,970,971],{"class":758},"return",[598,973,974],{"class":609}," false\n",[598,976,977,979,982,984,987,990,992,995,998,1001,1003,1006,1009,1012,1015,1018,1020,1023,1025,1028,1030,1033],{"class":600,"line":708},[598,978,825],{"class":758},[598,980,981],{"class":609}," expected",[598,983,831],{"class":758},[598,985,986],{"class":603}," crypto.",[598,988,989],{"class":785},"createHmac",[598,991,789],{"class":603},[598,993,994],{"class":616},"'sha256'",[598,996,997],{"class":603},", secret).",[598,999,1000],{"class":785},"update",[598,1002,789],{"class":603},[598,1004,1005],{"class":616},"`${",[598,1007,1008],{"class":603},"parts",[598,1010,1011],{"class":616},".",[598,1013,1014],{"class":603},"t",[598,1016,1017],{"class":616},"}.${",[598,1019,793],{"class":603},[598,1021,1022],{"class":616},"}`",[598,1024,851],{"class":603},[598,1026,1027],{"class":785},"digest",[598,1029,789],{"class":603},[598,1031,1032],{"class":616},"'hex'",[598,1034,1035],{"class":603},")\n",[598,1037,1039,1042,1044,1047,1050,1052,1055,1057],{"class":600,"line":1038},8,[598,1040,1041],{"class":758},"  return",[598,1043,986],{"class":603},[598,1045,1046],{"class":785},"timingSafeEqual",[598,1048,1049],{"class":603},"(Buffer.",[598,1051,765],{"class":785},[598,1053,1054],{"class":603},"(expected), Buffer.",[598,1056,765],{"class":785},[598,1058,1059],{"class":603},"(parts.v1))\n",[598,1061,1063],{"class":600,"line":1062},9,[598,1064,711],{"class":603},[589,1066,1070],{"className":1067,"code":1068,"language":1069,"meta":594,"style":594},"language-python shiki shiki-themes github-light github-dark","import hashlib, hmac, time\n\ndef verify_wagend(raw_body: bytes, header: str, secret: str) -> bool:\n    parts = dict(p.split(\"=\", 1) for p in header.split(\",\"))\n    if abs(time.time() - int(parts.get(\"t\", \"0\"))) > 300:\n        return False\n    signed = f\"{parts['t']}.\".encode() + raw_body\n    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(expected, parts.get(\"v1\", \"\"))\n","python",[496,1071,1072,1079,1083,1117,1159,1196,1204,1244,1254],{"__ignoreMap":594},[598,1073,1074,1076],{"class":600,"line":80},[598,1075,759],{"class":758},[598,1077,1078],{"class":603}," hashlib, hmac, time\n",[598,1080,1081],{"class":600,"line":86},[598,1082,774],{"emptyLinePlaceholder":773},[598,1084,1085,1088,1091,1094,1097,1100,1103,1106,1108,1111,1114],{"class":600,"line":623},[598,1086,1087],{"class":758},"def",[598,1089,1090],{"class":785}," verify_wagend",[598,1092,1093],{"class":603},"(raw_body: ",[598,1095,1096],{"class":609},"bytes",[598,1098,1099],{"class":603},", header: ",[598,1101,1102],{"class":609},"str",[598,1104,1105],{"class":603},", secret: ",[598,1107,1102],{"class":609},[598,1109,1110],{"class":603},") -> ",[598,1112,1113],{"class":609},"bool",[598,1115,1116],{"class":603},":\n",[598,1118,1119,1122,1125,1128,1131,1134,1136,1139,1141,1144,1147,1150,1153,1156],{"class":600,"line":636},[598,1120,1121],{"class":603},"    parts ",[598,1123,1124],{"class":758},"=",[598,1126,1127],{"class":609}," dict",[598,1129,1130],{"class":603},"(p.split(",[598,1132,1133],{"class":616},"\"=\"",[598,1135,684],{"class":603},[598,1137,1138],{"class":609},"1",[598,1140,862],{"class":603},[598,1142,1143],{"class":758},"for",[598,1145,1146],{"class":603}," p ",[598,1148,1149],{"class":758},"in",[598,1151,1152],{"class":603}," header.split(",[598,1154,1155],{"class":616},"\",\"",[598,1157,1158],{"class":603},"))\n",[598,1160,1161,1164,1167,1170,1173,1176,1179,1182,1184,1187,1190,1192,1194],{"class":600,"line":649},[598,1162,1163],{"class":758},"    if",[598,1165,1166],{"class":609}," abs",[598,1168,1169],{"class":603},"(time.time() ",[598,1171,1172],{"class":758},"-",[598,1174,1175],{"class":609}," int",[598,1177,1178],{"class":603},"(parts.get(",[598,1180,1181],{"class":616},"\"t\"",[598,1183,684],{"class":603},[598,1185,1186],{"class":616},"\"0\"",[598,1188,1189],{"class":603},"))) ",[598,1191,963],{"class":758},[598,1193,966],{"class":609},[598,1195,1116],{"class":603},[598,1197,1198,1201],{"class":600,"line":662},[598,1199,1200],{"class":758},"        return",[598,1202,1203],{"class":609}," False\n",[598,1205,1206,1209,1211,1214,1217,1220,1223,1226,1229,1232,1235,1238,1241],{"class":600,"line":708},[598,1207,1208],{"class":603},"    signed ",[598,1210,1124],{"class":758},[598,1212,1213],{"class":758}," f",[598,1215,1216],{"class":616},"\"",[598,1218,1219],{"class":609},"{",[598,1221,1222],{"class":603},"parts[",[598,1224,1225],{"class":616},"'t'",[598,1227,1228],{"class":603},"]",[598,1230,1231],{"class":609},"}",[598,1233,1234],{"class":616},".\"",[598,1236,1237],{"class":603},".encode() ",[598,1239,1240],{"class":758},"+",[598,1242,1243],{"class":603}," raw_body\n",[598,1245,1246,1249,1251],{"class":600,"line":1038},[598,1247,1248],{"class":603},"    expected ",[598,1250,1124],{"class":758},[598,1252,1253],{"class":603}," hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()\n",[598,1255,1256,1259,1262,1265,1267,1270],{"class":600,"line":1062},[598,1257,1258],{"class":758},"    return",[598,1260,1261],{"class":603}," hmac.compare_digest(expected, parts.get(",[598,1263,1264],{"class":616},"\"v1\"",[598,1266,684],{"class":603},[598,1268,1269],{"class":616},"\"\"",[598,1271,1158],{"class":603},[468,1273,407],{"id":1274},"reintentos",[573,1276,1277,1278,1281],{},"Respondé con cualquier ",[496,1279,1280],{},"2xx"," en menos de 10 segundos. Si no, reintentamos a los 1 min, 5 min, 30 min, 2 h y 12 h. Cada intento se ve en el log de entregas, donde también podés reenviar a mano.",[1283,1284,1285],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":594,"searchDepth":86,"depth":623,"links":1287},[1288,1289,1290,1291],{"id":470,"depth":86,"text":392},{"id":587,"depth":86,"text":397},{"id":725,"depth":86,"text":402},{"id":1274,"depth":86,"text":407},"md",{},"---\ntitle: Webhooks\ndescription: Eventos firmados de reservas, pagos y conversaciones, con reintentos automáticos.\nbadge: Borrador\n---\n\n## Eventos\n\n| Evento | Cuándo |\n| --- | --- |\n| `booking.created` | Se creó una pre-reserva o reserva |\n| `booking.confirmed` | Se confirmó una reserva (también después del pago) |\n| `booking.cancelled` | Cancelada o vencida |\n| `booking.rescheduled` | Se movió de horario (el payload trae el id viejo y el nuevo) |\n| `booking.no_show` | Marcada como ausencia |\n| `payment.paid` | Se recibió una seña por Pix |\n| `message.received` | Llegó un mensaje de un cliente |\n| `conversation.handoff` | Una conversación se derivó a una persona |\n\nCreá endpoints en **Desarrolladores → Webhooks** o con `POST \u002Fv1\u002Fwebhook-endpoints`. El secreto de firma se muestra una sola vez.\n\n## Payload\n\n```json\n{\n  \"id\": \"evt_01J9ZK3T6\",\n  \"type\": \"booking.confirmed\",\n  \"created_at\": \"2026-10-14T15:21:07-03:00\",\n  \"workspace_id\": \"ws_9f2c\",\n  \"data\": { \"booking\": { \"id\": \"bkg_7Qx1\", \"status\": \"confirmed\", \"start\": \"2026-10-15T09:45:00-03:00\" } }\n}\n```\n\nLa entrega es **al menos una vez**: usá el `id` para ignorar duplicados.\n\n## Verificar la firma\n\nCada request trae el header:\n\n```text\nWagend-Signature: t=1791040867,v1=5c2b9f...e81\n```\n\n`v1` es `HMAC-SHA256(secreto, t + \".\" + body_crudo)` en hexadecimal. Rechazá requests de más de 5 minutos.\n\n```ts\nimport crypto from 'node:crypto'\n\nexport function verifyWagend(rawBody: string, header: string, secret: string) {\n  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=') as [string, string]))\n  const age = Math.abs(Date.now() \u002F 1000 - Number(parts.t))\n  if (!parts.t || !parts.v1 || age > 300) return false\n  const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex')\n  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))\n}\n```\n\n```python\nimport hashlib, hmac, time\n\ndef verify_wagend(raw_body: bytes, header: str, secret: str) -> bool:\n    parts = dict(p.split(\"=\", 1) for p in header.split(\",\"))\n    if abs(time.time() - int(parts.get(\"t\", \"0\"))) > 300:\n        return False\n    signed = f\"{parts['t']}.\".encode() + raw_body\n    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(expected, parts.get(\"v1\", \"\"))\n```\n\n## Reintentos\n\nRespondé con cualquier `2xx` en menos de 10 segundos. Si no, reintentamos a los 1 min, 5 min, 30 min, 2 h y 12 h. Cada intento se ve en el log de entregas, donde también podés reenviar a mano.\n",{"title":65,"description":389},"_lM0qHXv5iHK9rotjfhTlmk8AZoX9EMiDCCUGdfBIp8",[1298,1300],{"title":61,"path":62,"stem":63,"description":1299,"children":-1},"Todos los endpoints de la v1 de un vistazo, con los formatos más comunes de request y respuesta.",{"title":69,"path":70,"stem":71,"description":1301,"children":-1},"Dejá que Claude, ChatGPT o tu propio agente busque horarios, reserve y opere un negocio por Model Context Protocol.",1790796586625]