[{"data":1,"prerenderedAt":779},["ShallowReactive",2],{"docs-nav-docs_en":3,"docs-search-docs_en":80,"doc-docs_en-\u002Fdocs\u002Fwebchat":497,"surround-docs_en-\u002Fdocs\u002Fwebchat":775},[4],{"title":5,"path":6,"stem":7,"children":8,"page":42},"Docs","\u002Fdocs","docs",[9,13,17,21,43,47,68,72,76],{"title":10,"path":11,"stem":12},"Introduction","\u002Fdocs\u002Fintroduction","docs\u002F1.introduction",{"title":14,"path":15,"stem":16},"Quickstart","\u002Fdocs\u002Fquickstart","docs\u002F2.quickstart",{"title":18,"path":19,"stem":20},"How it works","\u002Fdocs\u002Fhow-it-works","docs\u002F3.how-it-works",{"title":22,"path":23,"stem":24,"children":25,"page":42},"Concepts","\u002Fdocs\u002Fconcepts","docs\u002F4.concepts",[26,30,34,38],{"title":27,"path":28,"stem":29},"Workspaces and resources","\u002Fdocs\u002Fconcepts\u002Fworkspaces-and-resources","docs\u002F4.concepts\u002F1.workspaces-and-resources",{"title":31,"path":32,"stem":33},"Services and availability","\u002Fdocs\u002Fconcepts\u002Fservices-and-availability","docs\u002F4.concepts\u002F2.services-and-availability",{"title":35,"path":36,"stem":37},"Bookings and holds","\u002Fdocs\u002Fconcepts\u002Fbookings-and-holds","docs\u002F4.concepts\u002F3.bookings-and-holds",{"title":39,"path":40,"stem":41},"Automations","\u002Fdocs\u002Fconcepts\u002Fautomations","docs\u002F4.concepts\u002F4.automations",false,{"title":44,"path":45,"stem":46},"WhatsApp and AI","\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},"Authentication","\u002Fdocs\u002Fapi\u002Fauthentication","docs\u002F6.api\u002F1.authentication",{"title":57,"path":58,"stem":59},"Conventions","\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 and AI agents","\u002Fdocs\u002Fmcp","docs\u002F7.mcp",{"title":73,"path":74,"stem":75},"Recipes by business","\u002Fdocs\u002Frecipes","docs\u002F8.recipes",{"title":77,"path":78,"stem":79},"WebChat on your website","\u002Fdocs\u002Fwebchat","docs\u002F9.webchat",[81,85,91,96,101,106,109,114,119,124,129,134,139,144,149,152,157,162,167,172,175,180,185,190,195,198,203,208,213,218,223,226,231,236,241,246,251,254,259,264,269,274,279,284,287,292,297,302,307,312,317,320,325,330,335,338,343,348,353,358,363,366,371,376,381,386,391,396,399,404,409,414,419,424,427,432,437,442,447,450,455,460,465,470,475,478,483,488,493],{"id":11,"title":10,"titles":82,"content":83,"level":84},[],"What Wagend is, who it is for and the building blocks you will use. Wagend is a scheduling engine with WhatsApp + AI as its main channel. Your customers book by chatting; the system offers only slots that really exist, holds them, confirms them and sends reminders. Businesses manage everything from a dashboard, and developers integrate through a REST API and webhooks. Wagend is in development. This documentation describes the target contract (API v1 draft). Endpoints may still change before the pilot.",1,{"id":86,"title":87,"titles":88,"content":89,"level":90},"\u002Fdocs\u002Fintroduction#who-it-is-for","Who it is for",[10],"Any business that sells someone's or something's time: BusinessWhat gets bookedBarbershopA barber for 30–45 minutesAestheticsA professional and a machine and a room, at the same timeClinicA doctor and an officeRestaurantCovers in a dining room for a party sizeDeliveriesCapacity in a 2-hour window for a zoneClasses, courts, roomsA seat in a class or a court by the hour",2,{"id":92,"title":93,"titles":94,"content":95,"level":90},"\u002Fdocs\u002Fintroduction#building-blocks","Building blocks",[10],"Organization → your account. It holds one or more workspaces (a business or location).Resource → what gets consumed: a barber, a table, a laser machine, a room, a delivery zone.Service → what the customer books. It declares its requirements: which resources it needs, and how many, at the same time.Schedule → when each resource is available.Booking → goes through a hold (10 minutes) before being confirmed.Automation → reminders, confirmations and staff alerts triggered by booking events. Read Workspaces and resources for the full model.",{"id":97,"title":98,"titles":99,"content":100,"level":90},"\u002Fdocs\u002Fintroduction#channels","Channels",[10],"All channels use the same engine, so a slot booked on WhatsApp disappears instantly from the dashboard and the API. WhatsApp with an AI assistant (text and voice notes).Public booking page, embeddable widget and MCP for AI agents: Coming soon.Dashboard for the team (today's agenda, inbox, calendar, settings).REST API and webhooks for developers.",{"id":102,"title":103,"titles":104,"content":105,"level":90},"\u002Fdocs\u002Fintroduction#next-steps","Next steps",[10],"Quickstart: make your first booking through the API in 5 minutes.How it works: the architecture and the life of a message.MCP: the planned connection for AI agents (coming soon).",{"id":15,"title":14,"titles":107,"content":108,"level":84},[],"Create your first booking through the API in five minutes using a key for a test business. This guide creates a real booking, so use a production key (wg_live_...) from a business you set up for testing. wg_test_ keys are read-only today: a sandbox for writes is coming soon. To try the bot without WhatsApp, use the simulator in the dashboard. Base URL: https:\u002F\u002Fapi.wagend.app\u002Fv1. Every request needs Authorization: Bearer \u003Ckey>.",{"id":110,"title":111,"titles":112,"content":113,"level":90},"\u002Fdocs\u002Fquickstart#_1-get-a-key","1. Get a key",[14],"In the dashboard, go to Settings → Developers, create a Production key, and select the scopes slots:read, bookings:write and config:read. The key is shown only once. export WAGEND_KEY=\"wg_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxx\"",{"id":115,"title":116,"titles":117,"content":118,"level":90},"\u002Fdocs\u002Fquickstart#_2-check-who-you-are","2. Check who you are",[14],"curl https:\u002F\u002Fapi.wagend.app\u002Fv1\u002Fme -H \"Authorization: Bearer $WAGEND_KEY\" {\n  \"workspace\": { \"id\": \"ws_9f2c\", \"name\": \"Downtown Barbershop\", \"timezone\": \"America\u002FSao_Paulo\", \"locale\": \"pt\", \"currency\": \"BRL\" },\n  \"scopes\": [\"slots:read\", \"bookings:write\", \"config:read\"],\n  \"mode\": \"live\"\n} The workspace always comes from the key. You never send a workspace id.",{"id":120,"title":121,"titles":122,"content":123,"level":90},"\u002Fdocs\u002Fquickstart#_3-list-services","3. List services",[14],"curl https:\u002F\u002Fapi.wagend.app\u002Fv1\u002Fservices -H \"Authorization: Bearer $WAGEND_KEY\" {\n  \"data\": [\n    { \"id\": \"svc_corte\", \"name\": \"Haircut\", \"duration_min\": 30, \"price_cents\": 5000, \"currency\": \"BRL\",\n      \"requirements\": [{ \"resource_group_id\": \"grp_barbers\", \"units\": 1 }] }\n  ]\n}",{"id":125,"title":126,"titles":127,"content":128,"level":90},"\u002Fdocs\u002Fquickstart#_4-find-available-slots","4. Find available slots",[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":130,"title":131,"titles":132,"content":133,"level":90},"\u002Fdocs\u002Fquickstart#_5-hold-the-slot","5. Hold the slot",[14],"A hold reserves the slot for 10 minutes while you collect the customer's details. Always send an Idempotency-Key so retries are safe. 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":135,"title":136,"titles":137,"content":138,"level":90},"\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\": \"+5521999990000\", \"locale\": \"pt\" } }' { \"id\": \"bkg_7Qx1\", \"status\": \"confirmed\", \"start\": \"2026-10-15T09:45:00-03:00\", \"source\": \"api\" } If the service requires a deposit, the status is pending_payment. Charging the deposit through Pix is coming soon.",{"id":140,"title":141,"titles":142,"content":143,"level":90},"\u002Fdocs\u002Fquickstart#same-flow-in-javascript","Same flow in 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: '+5521999990000' } }),\n})",{"id":145,"title":146,"titles":147,"content":148,"level":90},"\u002Fdocs\u002Fquickstart#next","Next",[14],"React to bookings with webhooks.See every endpoint in the reference. 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":150,"content":151,"level":84},[],"Architecture, principles and the life of a WhatsApp message inside Wagend.",{"id":153,"title":154,"titles":155,"content":156,"level":90},"\u002Fdocs\u002Fhow-it-works#principles","Principles",[18],"Deterministic core, AI as interpreter. The AI understands the customer and calls tools. The engine computes availability, holds, and confirms. The AI never invents a time or a price.The database enforces the rules. Double booking is impossible because PostgreSQL rejects overlapping allocations, not because the app \"checks first\".API-first. The dashboard and the WhatsApp bot use the same services. If something can't be done through the API, it's missing from the API.The credential defines the tenant. A key or session always belongs to one workspace.",{"id":158,"title":159,"titles":160,"content":161,"level":90},"\u002Fdocs\u002Fhow-it-works#architecture","Architecture",[18],"Channels:   WhatsApp · Dashboard · REST\n                 │\nGateway:    credential → workspace · scopes · rate limits · signed webhooks\n                 │\nCore:       conversations + AI │ scheduling engine │ automations │ knowledge\n                 │\nData:       PostgreSQL (row-level security, exclusion constraints, jobs) · Redis\nExternal:   AI models (OpenRouter) · speech-to-text · WhatsApp provider",{"id":163,"title":164,"titles":165,"content":166,"level":90},"\u002Fdocs\u002Fhow-it-works#the-life-of-a-message","The life of a message",[18],"Arrives. The WhatsApp provider calls our webhook. We verify the HMAC signature, drop duplicates by message id and store it. We answer in under 200 ms.Understood. Voice notes are transcribed. We identify the customer and the conversation. If a person took over, the bot stays silent.The AI asks. The model gets the business context, the local date and time, a structured conversation state and the last messages. It can only call tools such as find_slots, hold_slot or confirm_hold.The engine decides. Each tool call is validated against a strict schema and business rules, then executed. Errors come back as structured data (for example slot_taken with alternatives) so the AI asks again.Exact reply. Dates, times, services, prices and addresses are rendered from templates filled with the real result.Events. booking.confirmed and friends trigger automations (reminders) and your webhooks.",{"id":168,"title":169,"titles":170,"content":171,"level":90},"\u002Fdocs\u002Fhow-it-works#where-to-go-next","Where to go next",[18],"Bookings and holds explains the guarantees.WhatsApp and AI explains what the AI can and cannot do.",{"id":28,"title":27,"titles":173,"content":174,"level":84},[],"Organizations, workspaces, roles, resources, capacity modes and resource groups.",{"id":176,"title":177,"titles":178,"content":179,"level":90},"\u002Fdocs\u002Fconcepts\u002Fworkspaces-and-resources#organization-and-workspaces","Organization and workspaces",[27],"An organization is the account that pays. It contains one or more workspaces. A workspace is a business or a location with its own: time zone, language and currency,WhatsApp number(s),bot, services, resources and team. A chain of three barbershops is one organization with three workspaces.",{"id":181,"title":182,"titles":183,"content":184,"level":90},"\u002Fdocs\u002Fconcepts\u002Fworkspaces-and-resources#roles","Roles",[27],"RoleCanownerEverything, across all workspaces of the organizationmanagerEverything inside one workspacestaffSee and manage only their own resources (for example, a barber sees only their agenda)viewerRead-only Staff can also act from WhatsApp after linking their number with a one-time code. Admin actions from WhatsApp always ask for a YES\u002FNO confirmation.",{"id":186,"title":187,"titles":188,"content":189,"level":90},"\u002Fdocs\u002Fconcepts\u002Fworkspaces-and-resources#resources","Resources",[27],"A resource is anything that gets consumed by a booking. kindExamplesstaffBarber, doctor, beautician, instructorspaceRoom, office, booth, dining roomequipmentLaser machine, chair, courttableRestaurant table (table mode)vehicleVan, delivery crewzoneDelivery area by postal code Each resource has a capacity mode: exclusive — serves one booking at a time (a barber, a room, a machine).pooled — has a capacity in units (40 covers per slot, 12 seats in a class, 10 deliveries per window).",{"id":191,"title":192,"titles":193,"content":194,"level":90},"\u002Fdocs\u002Fconcepts\u002Fworkspaces-and-resources#resource-groups","Resource groups",[27],"A resource group is an interchangeable pool (\"any barber\", \"laser machines\"). Services require groups, not individual resources, so the engine can pick one. The assignment strategy decides which: assignmentBehaviorcustomer_choiceThe customer picks (for example \"with Juan\"); falls back to another strategy if they don't careround_robinRotates between membersleast_busyPicks the member with the fewest bookings that dayfixedAlways the same member (single machine, single room) {\n  \"name\": \"Barbers\",\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":196,"content":197,"level":84},[],"Services, multi-resource requirements, schedules and how slots are computed.",{"id":199,"title":200,"titles":201,"content":202,"level":90},"\u002Fdocs\u002Fconcepts\u002Fservices-and-availability#services","Services",[31],"A service is what the customer books. FieldMeaningduration_minLength of the servicestep_minGrid for start times (every 15, 30 minutes…)buffer_before_min \u002F buffer_after_minCleanup or preparation time blocked around the bookingprice_cents, currencyPrice shown by the bot (always from the database)deposit_centsDeposit required to confirm (charging through Pix: coming soon)lead_time_minMinimum notice (for example 60 minutes)max_advance_daysHow far ahead customers can bookwindow_modeThe slot is a fixed window (deliveries: 14:00–16:00)fulfillment_modescheduled (default, with a time slot) or queue (dispatch by queue, no fixed time)intake_formJSON Schema of extra data to collect (insurance, address…)requirementsWhich resources the service needs at the same time",{"id":204,"title":205,"titles":206,"content":207,"level":90},"\u002Fdocs\u002Fconcepts\u002Fservices-and-availability#window-or-dispatch-two-fulfillment-modes","Window or dispatch: two fulfillment modes",[31],"Every service defines how the booking is fulfilled (fulfillment_mode): scheduled (the default): the booking occupies a known time range — a fixed slot or, with window_mode, a window (deliveries from 14:00 to 16:00, with capacity per zone).queue (dispatch, for water, gas or on-demand delivery): the booking has no fixed time — it joins a courier's backlog queue. The engine assigns it to the courier with the fewest active tasks, and each courier has a cap on active tasks at a time, enforced in the database. The queue uses the same states as a booking: pending → accepted → started → completed (or failed). So the same delivery business can offer scheduled windows, immediate dispatch, or both.",{"id":209,"title":210,"titles":211,"content":212,"level":90},"\u002Fdocs\u002Fconcepts\u002Fservices-and-availability#requirements","Requirements",[31],"A requirement says: from this group, I need this many units. A service can have several; a slot exists only if all of them are free. {\n  \"name\": \"Laser hair removal\",\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} With two professionals but only one laser, Wagend will never offer two overlapping laser sessions. Advanced options: units: \"party_size\" — consume as many units as people (restaurants, classes).offset_min \u002F duration_min on a requirement — use a resource for part of the service only (for example the wash basin during the last 15 minutes of a hair color).",{"id":214,"title":215,"titles":216,"content":217,"level":90},"\u002Fdocs\u002Fconcepts\u002Fservices-and-availability#schedules","Schedules",[31],"Each resource has a schedule: weekly rules in the resource's time zone plus overrides for specific dates (closed, or special hours). {\n  \"tz\": \"America\u002FSao_Paulo\",\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":219,"title":220,"titles":221,"content":222,"level":90},"\u002Fdocs\u002Fconcepts\u002Fservices-and-availability#how-slots-are-computed","How slots are computed",[31],"Expand the schedules for the requested range, in each resource's time zone.Remove active bookings and holds (plus buffers) and manual blocks.Cut into candidate slots using the service duration and step.Apply lead time, max advance, cutoff rules and party size.Cross all requirements, trying group members according to the assignment strategy.Return a short, ordered list. When nothing is available, the response includes unavailable_reason (closed, no_staff, no_equipment, no_space, lead_time, max_advance, cutoff, full) so the bot can explain why. 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":224,"content":225,"level":84},[],"Booking states, the 10-minute hold, deposits and the no-double-booking guarantee.",{"id":227,"title":228,"titles":229,"content":230,"level":90},"\u002Fdocs\u002Fconcepts\u002Fbookings-and-holds#why-a-hold","Why a hold",[35],"A WhatsApp conversation can take minutes: the customer picks a time, then you ask their name, then maybe a deposit. Meanwhile other customers and the API are competing for the same slot. A hold reserves the slot for 10 minutes (15 when a deposit is required) and expires on its own.",{"id":232,"title":233,"titles":234,"content":235,"level":90},"\u002Fdocs\u002Fconcepts\u002Fbookings-and-holds#states","States",[35],"StatusMeaningOccupies the slotheldTemporary reservationYespending_paymentWaiting for the depositYesconfirmedBookedYeschecked_inThe customer arrivedYescompletedService doneNocancelledCancelled (or replaced by a reschedule)Nono_showMarked absent by a personNoexpiredHold not confirmed in timeNo held ──confirm──► confirmed ──check-in──► checked_in ──► completed\n │  └─(deposit)─► pending_payment ──paid──► confirmed\n └─expire──► expired          confirmed ──cancel──► cancelled",{"id":237,"title":238,"titles":239,"content":240,"level":90},"\u002Fdocs\u002Fconcepts\u002Fbookings-and-holds#guarantees","Guarantees",[35],"Every resource a booking occupies is stored as an allocation with a time range.For exclusive resources, PostgreSQL rejects overlapping allocations with an exclusion constraint.For pooled resources, capacity counters can never exceed the limit (checked atomically).A multi-resource booking is written in a single transaction: if any resource clashes, nothing is booked.POST \u002Fholds and POST \u002Fholds\u002F{id}\u002Fconfirm require an Idempotency-Key: retrying a request never creates a second booking. If a slot was taken while the customer was deciding, you get 409 with code: \"slot_taken\" and a list of alternatives.",{"id":242,"title":243,"titles":244,"content":245,"level":90},"\u002Fdocs\u002Fconcepts\u002Fbookings-and-holds#reschedule-and-cancel","Reschedule and cancel",[35],"Reschedule creates a new booking and cancels the old one (linked by rescheduled_to). Reminders move automatically.Cancel frees the slot and cancels pending reminders.No-show is never automatic: the system suggests it, a person confirms it.",{"id":247,"title":248,"titles":249,"content":250,"level":90},"\u002Fdocs\u002Fconcepts\u002Fbookings-and-holds#deposits","Deposits",[35],"If the service has deposit_cents, confirming a hold leaves the booking as pending_payment and the slot stays blocked. Charging the deposit through Pix is coming soon: until it ships, the booking does not move to confirmed on its own and payment.paid is not emitted.",{"id":40,"title":39,"titles":252,"content":253,"level":84},[],"Reminders, confirmations and alerts as \"when this happens, do that\" rules. Automations are rules made of a trigger, optional conditions and an action. Each business creates, turns on and adjusts its own.",{"id":255,"title":256,"titles":257,"content":258,"level":90},"\u002Fdocs\u002Fconcepts\u002Fautomations#triggers","Triggers",[39],"TriggerExamplebooking.confirmedSend the booking summarybooking.starts_in(Δ)For example 24 h before: send a reminderbooking.ended(+Δ)After the booking ends: send a messagebooking.cancelled, booking.rescheduledNotify the customerbooking.stage_changedWhen the booking moves to another stage",{"id":260,"title":261,"titles":262,"content":263,"level":90},"\u002Fdocs\u002Fconcepts\u002Fautomations#conditions","Conditions",[39],"Service, resource, party size above N, channel.",{"id":265,"title":266,"titles":267,"content":268,"level":90},"\u002Fdocs\u002Fconcepts\u002Fautomations#actions","Actions",[39],"Send a message or template to the customer, notify staff, request a YES\u002FNO confirmation, change the booking status.",{"id":270,"title":271,"titles":272,"content":273,"level":90},"\u002Fdocs\u002Fconcepts\u002Fautomations#starting-rules","Starting rules",[39],"Business templates do not turn rules on by themselves: each business creates, turns on and adjusts its own.",{"id":275,"title":276,"titles":277,"content":278,"level":90},"\u002Fdocs\u002Fconcepts\u002Fautomations#coming-soon","Coming soon",[39],"These do not exist yet: the hold.expired, payment.paid and daily_at(hh:mm) (daily agenda to staff) triggers; conditions by customer tag and by number of past no-shows; and the actions to create an internal task and to call a webhook.",{"id":280,"title":281,"titles":282,"content":283,"level":90},"\u002Fdocs\u002Fconcepts\u002Fautomations#execution-guarantees","Execution guarantees",[39],"Jobs are created in the same transaction as the booking event, so they are never lost.Before running, a job re-checks the booking. If it was rescheduled or cancelled, the old job is discarded.Each job runs once (idempotency key) and retries with backoff; after 5 failures it shows up in the dashboard.Quiet hours: nothing is sent to customers between 21:00 and 08:00 in their time zone.YES\u002FNO answers are parsed without AI (sim, sí, ok, 👍, não…), which is instant and free.",{"id":45,"title":44,"titles":285,"content":286,"level":84},[],"Connecting a number, what the assistant can and cannot do, staff actions and human handoff.",{"id":288,"title":289,"titles":290,"content":291,"level":90},"\u002Fdocs\u002Fwhatsapp-and-ai#connecting-a-number","Connecting a number",[44],"In the dashboard, go to Channels → WhatsApp → Connect and scan the QR code with the business phone, just like WhatsApp Web. A workspace can connect several numbers (one per location, for example). Use a number dedicated to the business. Today a QR connection is the only way to connect.",{"id":293,"title":294,"titles":295,"content":296,"level":90},"\u002Fdocs\u002Fwhatsapp-and-ai#customers-vs-staff","Customers vs staff",[44],"The role is decided in code, never by what someone says in the chat: A number linked to a team member (verified with a one-time code) gets staff tools.Everyone else gets customer tools. Customer toolsStaff tools (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 — always with a YES\u002FNO confirmation",{"id":298,"title":299,"titles":300,"content":301,"level":90},"\u002Fdocs\u002Fwhatsapp-and-ai#what-the-ai-can-and-cannot-do","What the AI can and cannot do",[44],"CanCannotUnderstand text, voice notes, typos and changes of mindInvent times, prices or addressesFind slots and offer 2–3 optionsConfirm a booking without the engineHold a slot and ask for missing detailsUse admin tools with a customerAnswer questions from the business knowledge baseFollow instructions hidden in messages or filesHand over to a personSee another business's data Workspace ids, customer ids and prices are injected by the system; the model never supplies them.",{"id":303,"title":304,"titles":305,"content":306,"level":90},"\u002Fdocs\u002Fwhatsapp-and-ai#human-handoff","Human handoff",[44],"The team sees every conversation live in the Inbox. Taking over pauses the bot for that conversation; it resumes automatically after 15 minutes without activity from the operator. The assistant also hands over on request (\"I want to talk to a person\").",{"id":308,"title":309,"titles":310,"content":311,"level":90},"\u002Fdocs\u002Fwhatsapp-and-ai#voice-notes-and-languages","Voice notes and languages",[44],"Voice notes are transcribed before the AI reads them, and the transcript is shown in the inbox. The assistant replies in the customer's language (Portuguese, Spanish or English).",{"id":313,"title":314,"titles":315,"content":316,"level":90},"\u002Fdocs\u002Fwhatsapp-and-ai#models","Models",[44],"The AI layer runs through OpenRouter with a dedicated key and spending limit per workspace. The default is a fast, low-cost model; a stronger model is used only for turns that fail validation. Providers are pinned to ones that do not retain data.",{"id":54,"title":53,"titles":318,"content":319,"level":84},[],"API keys, scopes, test and live modes.",{"id":321,"title":322,"titles":323,"content":324,"level":90},"\u002Fdocs\u002Fapi\u002Fauthentication#api-keys","API keys",[53],"Send your key as a bearer token: GET \u002Fv1\u002Fme HTTP\u002F1.1\nHost: api.wagend.app\nAuthorization: Bearer wg_live_3fa9c2_Lr8t... PrefixModewg_live_Production data and real messageswg_test_Read-only: it can list and read, not create or change anything. A sandbox for writes is coming soon Keys belong to one workspace. The workspace is always taken from the key: there is no workspace id in paths or bodies. Keys are stored hashed; the full key is shown only once at creation.",{"id":326,"title":327,"titles":328,"content":329,"level":90},"\u002Fdocs\u002Fapi\u002Fauthentication#scopes","Scopes",[53],"ScopeAllowsslots:readGET \u002Fslotsbookings:readList and read bookingsbookings:writeHolds, confirm, cancel, reschedule, check-in, no-showcustomers:readRead customersmessages:readRead conversations and messagesmessages:writeSend messages, hand overconfig:read \u002F config:writeServices, resources, groups, schedules, automations, knowledgewebhooks:manageWebhook endpoints A request without the needed scope returns 403 with code: \"insufficient_scope\".",{"id":331,"title":332,"titles":333,"content":334,"level":90},"\u002Fdocs\u002Fapi\u002Fauthentication#rotation","Rotation",[53],"Create a new key, deploy it, then revoke the old one in Settings → Developers. Revoked keys return 401 immediately. 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":336,"content":337,"level":84},[],"Formats, idempotency, pagination, errors and rate limits.",{"id":339,"title":340,"titles":341,"content":342,"level":90},"\u002Fdocs\u002Fapi\u002Fconventions#basics","Basics",[57],"Base URL https:\u002F\u002Fapi.wagend.app\u002Fv1. Breaking changes ship as a new version.JSON in and out (Content-Type: application\u002Fjson).Timestamps in ISO 8601 with offset (2026-10-15T09:45:00-03:00). Stored in UTC.Money in integer cents plus currency (BRL, ARS, PYG, USD).IDs are opaque strings with a prefix (bkg_, svc_, res_, grp_, cus_).",{"id":344,"title":345,"titles":346,"content":347,"level":90},"\u002Fdocs\u002Fapi\u002Fconventions#idempotency","Idempotency",[57],"POST \u002Fholds, POST \u002Fholds\u002F{id}\u002Fconfirm, POST \u002Fbookings and POST \u002Fbookings\u002F{id}\u002Freschedule require an Idempotency-Key header (for example a UUID). Repeating a request with the same key within 24 hours returns the original response. Reusing a key with a different body returns 422.",{"id":349,"title":350,"titles":351,"content":352,"level":90},"\u002Fdocs\u002Fapi\u002Fconventions#pagination","Pagination",[57],"List endpoints use cursors: 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":354,"title":355,"titles":356,"content":357,"level":90},"\u002Fdocs\u002Fapi\u002Fconventions#errors","Errors",[57],"Errors follow 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} codeStatusWheninvalid_request400 \u002F 422Validation failedunauthorized401Missing, invalid or revoked keyinsufficient_scope403Key lacks the scopenot_found404Unknown id (or belongs to another workspace)slot_taken409Someone else got the slotinvalid_transition409For example confirming a cancelled bookinghold_expired410The hold expired before confirmidempotency_mismatch422Same key, different bodyrate_limited429Too many requests",{"id":359,"title":360,"titles":361,"content":362,"level":90},"\u002Fdocs\u002Fapi\u002Fconventions#rate-limits","Rate limits",[57],"Limits apply per key and per workspace. Every response includes RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. On 429, wait Retry-After seconds. 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":364,"content":365,"level":84},[],"Every v1 endpoint at a glance, with the most common request and response shapes. The machine-readable contract lives in the repository at packages\u002Fopenapi\u002Fopenapi.yaml (OpenAPI 3.1).",{"id":367,"title":368,"titles":369,"content":370,"level":90},"\u002Fdocs\u002Fapi\u002Fendpoints#scheduling","Scheduling",[61],"MethodPathScopeDescriptionGET\u002Fslotsslots:readAvailable slots for a servicePOST\u002Fholdsbookings:writeCreate a 10-minute holdPOST\u002Fholds\u002F{id}\u002Fconfirmbookings:writeConfirm (or pending_payment if a deposit is required)DELETE\u002Fholds\u002F{id}bookings:writeRelease a holdGET\u002Fbookingsbookings:readList (filters: from, to, status, resource_id, customer_id)POST\u002Fbookingsbookings:writeHold + confirm in one callGET \u002F PATCH\u002Fbookings\u002F{id}bookings:read \u002F writeRead, update notes or intake dataPOST\u002Fbookings\u002F{id}\u002Fcancelbookings:writeCancelPOST\u002Fbookings\u002F{id}\u002Freschedulebookings:writeMove to a new startPOST\u002Fbookings\u002F{id}\u002Fcheck-inbookings:writeMark arrivalPOST\u002Fbookings\u002F{id}\u002Fno-showbookings:writeMark absence",{"id":372,"title":373,"titles":374,"content":375,"level":90},"\u002Fdocs\u002Fapi\u002Fendpoints#catalog","Catalog",[61],"MethodPathScopeGET \u002F POST\u002Fservicesconfig:read \u002F config:writeGET \u002F POST\u002Fresourcesconfig:read \u002F config:writeGET\u002Fresource-groupsconfig:readPOST\u002Fschedules\u002F{id}\u002Foverridesconfig:write",{"id":377,"title":378,"titles":379,"content":380,"level":90},"\u002Fdocs\u002Fapi\u002Fendpoints#customers-and-conversations","Customers and conversations",[61],"MethodPathScopeGET\u002Fcustomerscustomers:readGET\u002Fconversationsmessages:readGET \u002F POST\u002Fconversations\u002F{id}\u002Fmessagesmessages:read \u002F messages:write",{"id":382,"title":383,"titles":384,"content":385,"level":90},"\u002Fdocs\u002Fapi\u002Fendpoints#platform","Platform",[61],"MethodPathScopeGET\u002FmeanyGET \u002F POST\u002Fwebhook-endpointswebhooks:manage",{"id":387,"title":388,"titles":389,"content":390,"level":90},"\u002Fdocs\u002Fapi\u002Fendpoints#example-slots","Example: 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":392,"title":393,"titles":394,"content":395,"level":90},"\u002Fdocs\u002Fapi\u002Fendpoints#example-booking-object","Example: booking object",[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  \"source\": \"whatsapp\",\n  \"created_at\": \"2026-10-19T11:02:13-03:00\"\n} Allocations include the buffer time (here 10 minutes after the service). 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":397,"content":398,"level":84},[],"Signed events for bookings and conversations, with automatic retries.",{"id":400,"title":401,"titles":402,"content":403,"level":90},"\u002Fdocs\u002Fapi\u002Fwebhooks#events","Events",[65],"EventWhenbooking.createdA hold or booking was createdbooking.confirmedA booking was confirmedbooking.cancelledCancelled or expiredbooking.rescheduledMoved to a new time (payload has the old and new ids)booking.no_showMarked as absentbooking.completedAppointment completedpayment.paidReserved: not emitted yet (Pix deposits: coming soon)message.receivedA customer message arrivedconversation.handoffA conversation was handed to a person Create endpoints with POST \u002Fv1\u002Fwebhook-endpoints. The signing secret is shown once.",{"id":405,"title":406,"titles":407,"content":408,"level":90},"\u002Fdocs\u002Fapi\u002Fwebhooks#payload","Payload",[65],"{\n  \"id\": \"evt_3f6c1d9e0b7a4c2f8e5d1a9b7c3e6f20\",\n  \"type\": \"booking.confirmed\",\n  \"created_at\": \"2026-10-14T15:21:07-03:00\",\n  \"workspace_id\": \"b3a6c8e2-5f1d-4c7a-9e0b-2d8f4a1c6e53\",\n  \"data\": { \"booking\": { \"id\": \"6d1f0c4a-7b2e-4a58-9c3d-0e5f8a2b1c47\", \"status\": \"confirmed\", \"start_at\": \"2026-10-15T12:45:00+00:00\" } }\n} Delivery is at least once: use id to ignore duplicates.",{"id":410,"title":411,"titles":412,"content":413,"level":90},"\u002Fdocs\u002Fapi\u002Fwebhooks#verifying-the-signature","Verifying the signature",[65],"Every request has a header: Wagend-Signature: t=1791040867,v1=5c2b9f...e81 v1 is HMAC-SHA256(secret, t + \".\" + raw_body) in hex. Reject requests older than 5 minutes. 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":415,"title":416,"titles":417,"content":418,"level":90},"\u002Fdocs\u002Fapi\u002Fwebhooks#retries","Retries",[65],"Respond with any 2xx within 10 seconds. Otherwise we retry after 1 min, 5 min, 30 min, 2 h and 12 h. Every attempt is visible in the delivery log, where you can also resend manually.",{"id":420,"title":421,"titles":422,"content":423,"level":90},"\u002Fdocs\u002Fapi\u002Fwebhooks#security-and-limits","Security and limits",[65],"The URL must be https and resolve to a public address: we reject localhost, private networks, link-local and cloud metadata addresses (when you register it and on every delivery). Redirects are not followed (a 3xx counts as a failure).Extra headers: Wagend-Event (type) and Wagend-Delivery (delivery id). Every attempt is signed with a fresh timestamp.Up to 10 endpoints per workspace. After 10 consecutive failures the endpoint is disabled (re-enable it with PATCH \u002Fv1\u002Fwebhook-endpoints\u002F{id} and {\"active\": true}).POST \u002Fv1\u002Fwebhook-endpoints\u002F{id}\u002Fping sends a webhook.ping test event; POST \u002Fv1\u002Fwebhook-endpoints\u002F{id}\u002Frotate-secret issues a new secret (shown once; the old one stops working immediately).With an API key use the webhooks:manage scope. The log is at GET \u002Fv1\u002Fwebhook-endpoints\u002F{id}\u002Fdeliveries and resending at POST …\u002Fdeliveries\u002F{delivery_id}\u002Fresend. 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":425,"content":426,"level":84},[],"Let Claude, ChatGPT or your own agent find slots, book and run a business through the Model Context Protocol. Coming soon: the MCP server is not available yet. This page describes what is planned. Wagend exposes an MCP server so AI agents can use the same engine as the WhatsApp bot: same holds, same validation, same audit log.",{"id":428,"title":429,"titles":430,"content":431,"level":90},"\u002Fdocs\u002Fmcp#connect","Connect",[69],"URL: https:\u002F\u002Fmcp.wagend.app (Streamable HTTP)Auth: an API key in the Authorization header. OAuth will follow. Example client configuration: {\n  \"mcpServers\": {\n    \"wagend\": {\n      \"type\": \"http\",\n      \"url\": \"https:\u002F\u002Fmcp.wagend.app\",\n      \"headers\": { \"Authorization\": \"Bearer wg_live_...\" }\n    }\n  }\n}",{"id":433,"title":434,"titles":435,"content":436,"level":90},"\u002Fdocs\u002Fmcp#toolsets","Toolsets",[69],"The tools an agent sees depend on the key's scopes. Booking (slots:read, bookings:write) — for assistants booking on behalf of a customer: ToolDoeslist_servicesServices with duration and pricefind_slotsAvailable slots for a service and date rangehold_slotHold a slot for 10 minutesconfirm_bookingConfirm a hold with customer detailscancel_booking, reschedule_booking, get_bookingManage an existing booking Admin (config:write) — for the owner running the business from their AI assistant: ToolDoeslist_resources, list_todayTeam, rooms, machines and today's agendablock_timeBlock a resource (vacation, maintenance)create_service, update_scheduleChange the catalog and hoursget_statsBookings, occupancy and no-shows",{"id":438,"title":439,"titles":440,"content":441,"level":90},"\u002Fdocs\u002Fmcp#example-prompts","Example prompts",[69],"\"Book me a haircut with Juan tomorrow morning, name Carlos.\"\"Block the laser machine next Monday for maintenance and tell me which bookings are affected.\"\"How many no-shows did we have this month?\"",{"id":443,"title":444,"titles":445,"content":446,"level":90},"\u002Fdocs\u002Fmcp#docs-for-llms","Docs for LLMs",[69],"\u002Fllms.txt lists every documentation page.\u002Fllms-full.txt contains the full English documentation in 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":448,"content":449,"level":84},[],"How to model a barbershop, an aesthetics clinic, a medical office, a restaurant and deliveries. Each recipe matches a ready-made template you can pick during onboarding.",{"id":451,"title":452,"titles":453,"content":454,"level":90},"\u002Fdocs\u002Frecipes#barbershop","Barbershop",[73],"Each barber is an exclusive staff resource with their own schedule. One group, customer_choice with a round_robin fallback. {\n  \"name\": \"Haircut\",\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":456,"title":457,"titles":458,"content":459,"level":90},"\u002Fdocs\u002Frecipes#aesthetics","Aesthetics",[73],"Professionals, machines and rooms are separate resources. A service requires all three at the same time, so the scarcest one (usually the machine) limits the agenda. {\n  \"name\": \"Laser hair removal\",\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":461,"title":462,"titles":463,"content":464,"level":90},"\u002Fdocs\u002Frecipes#clinic","Clinic",[73],"Doctor plus office, per location (one workspace per location). Health data is sensitive: the assistant never asks about symptoms, and the intake form collects only what scheduling needs. {\n  \"name\": \"Appointment\",\n  \"duration_min\": 30,\n  \"buffer_after_min\": 10,\n  \"intake_form\": { \"type\": \"object\", \"properties\": { \"insurance\": { \"type\": \"string\" } } },\n  \"requirements\": [\n    { \"resource_group_id\": \"grp_doctors\", \"units\": 1 },\n    { \"resource_group_id\": \"grp_offices\", \"units\": 1 }\n  ]\n}",{"id":466,"title":467,"titles":468,"content":469,"level":90},"\u002Fdocs\u002Frecipes#restaurant","Restaurant",[73],"The dining room is one pooled resource with 40 units (covers) per slot. Duration grows with the party size, and large parties require a deposit. {\n  \"name\": \"Table\",\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} Table mode (specific tables with minimum and maximum size, and combinations) is on the roadmap.",{"id":471,"title":472,"titles":473,"content":474,"level":90},"\u002Fdocs\u002Frecipes#deliveries","Deliveries",[73],"Each zone is a pooled resource with a capacity per 2-hour window. The zone is selected by postal code, and a cutoff rule closes same-day windows at noon. {\n  \"name\": \"Delivery\",\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":78,"title":77,"titles":476,"content":477,"level":84},[],"Put the Wagend assistant on your site with a single script line, no WhatsApp number needed. WebChat is a chat widget your business pastes into its own website. It talks to the same bot as WhatsApp (same schedules, prices and bookings) but needs no phone number or third-party approval: ideal for trying things out and as a storefront on your site.",{"id":479,"title":480,"titles":481,"content":482,"level":90},"\u002Fdocs\u002Fwebchat#install","Install",[77],"In the dashboard, as owner, publish WebChat and list the allowed domains (for example https:\u002F\u002Fwww.myshop.com). You get a publishable key wg_web_….Paste the snippet before \u003C\u002Fbody>: \u003Cscript src=\"https:\u002F\u002Fwagend.app\u002Fwidget.js\" data-key=\"wg_web_…\" defer>\u003C\u002Fscript> Optional attributes: data-locale (pt, es or en; defaults to the browser language) and data-api (API origin, for test environments only). Color, position and title come from the workspace configuration. There is a demo page at \u002Fdemo\u002Fwebchat where you paste the key and see the widget running.",{"id":484,"title":485,"titles":486,"content":487,"level":90},"\u002Fdocs\u002Fwebchat#what-the-visitor-sees","What the visitor sees",[77],"A floating button that opens the chat; texts in Portuguese, Spanish or English.Name and phone are optional and not verified: they do not identify the visitor and are never merged with existing customers.The conversation lives in page memory: reloading starts a new one. Nothing is stored in the browser.",{"id":489,"title":490,"titles":491,"content":492,"level":90},"\u002Fdocs\u002Fwebchat#security","Security",[77],"The publishable key only opens anonymous conversations: it gives no access to the dashboard, the private API or customer data. It can be revoked at any time (and rotates when you publish again).The workspace always comes from the key; the widget never sends a workspace_id.Only allowed domains can use the chat (per-workspace CORS). No cookies.The Origin check is not authentication. It only protects against other websites in a browser; any non-browser client (a script, curl) can send whatever Origin it likes. That is why the publishable key is not a secret and the real protection is the usage limits, not the origin.Cost caps: messages per conversation and per hour, daily messages per workspace, new conversations per hour and simultaneous live connections. When one is exhausted the API answers 429 with Retry-After and the assistant is not invoked. Idle conversations expire on their own and are purged, including the visitor's declared name.Usage limits per IP, per conversation and per workspace; messages up to 1000 characters.Visitor text is untrusted data for the assistant, and the widget renders all text as plain text (never HTML).The chat is reactive only: it sends no reminders or proactive messages.",{"id":494,"title":48,"titles":495,"content":496,"level":90},"\u002Fdocs\u002Fwebchat#api",[77],"Management (owner, panel cookie): GET|PUT|DELETE \u002Fv1\u002Fwebchat-site. Public: GET \u002Fv1\u002Fpublic\u002Fwebchat\u002Fconfig, POST \u002Fv1\u002Fpublic\u002Fwebchat\u002Fsessions, GET|POST \u002Fv1\u002Fpublic\u002Fwebchat\u002Fsessions\u002F{id}\u002Fmessages and GET \u002Fv1\u002Fpublic\u002Fwebchat\u002Fsessions\u002F{id}\u002Fevents (SSE). The contract lives in packages\u002Fopenapi\u002Fopenapi.yaml. Disabled by default (PUBLIC_WEBCHAT_ENABLED=false) until the security review and explicit activation. html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}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 .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":498,"title":77,"badge":499,"body":500,"description":768,"extension":769,"meta":770,"navigation":771,"path":78,"rawbody":772,"seo":773,"stem":79,"__hash__":774},"docs_en\u002Fdocs\u002F9.webchat.md","Draft",{"type":501,"value":502,"toc":761},"minimark",[503,512,516,551,602,625,636,639,659,662,721,724,747,757],[504,505,506,507,511],"p",{},"WebChat is a chat widget your business pastes into its own website. It talks to the ",[508,509,510],"strong",{},"same bot"," as WhatsApp (same schedules, prices and bookings) but needs no phone number or third-party approval: ideal for trying things out and as a storefront on your site.",[513,514,480],"h2",{"id":515},"install",[517,518,519,544],"ol",{},[520,521,522,523,526,527,530,531,535,536,539,540,543],"li",{},"In the dashboard, as ",[508,524,525],{},"owner",", publish WebChat and list the ",[508,528,529],{},"allowed domains"," (for example ",[532,533,534],"code",{},"https:\u002F\u002Fwww.myshop.com","). You get a ",[508,537,538],{},"publishable key"," ",[532,541,542],{},"wg_web_…",".",[520,545,546,547,550],{},"Paste the snippet before ",[532,548,549],{},"\u003C\u002Fbody>",":",[552,553,558],"pre",{"className":554,"code":555,"language":556,"meta":557,"style":557},"language-html shiki shiki-themes github-light github-dark","\u003Cscript src=\"https:\u002F\u002Fwagend.app\u002Fwidget.js\" data-key=\"wg_web_…\" defer>\u003C\u002Fscript>\n","html","",[532,559,560],{"__ignoreMap":557},[561,562,564,568,572,576,579,583,586,588,591,594,597,599],"span",{"class":563,"line":84},"line",[561,565,567],{"class":566},"sVt8B","\u003C",[561,569,571],{"class":570},"s9eBZ","script",[561,573,575],{"class":574},"sScJk"," src",[561,577,578],{"class":566},"=",[561,580,582],{"class":581},"sZZnC","\"https:\u002F\u002Fwagend.app\u002Fwidget.js\"",[561,584,585],{"class":574}," data-key",[561,587,578],{"class":566},[561,589,590],{"class":581},"\"wg_web_…\"",[561,592,593],{"class":574}," defer",[561,595,596],{"class":566},">\u003C\u002F",[561,598,571],{"class":570},[561,600,601],{"class":566},">\n",[504,603,604,605,608,609,612,613,616,617,620,621,624],{},"Optional attributes: ",[532,606,607],{},"data-locale"," (",[532,610,611],{},"pt",", ",[532,614,615],{},"es"," or ",[532,618,619],{},"en","; defaults to the browser language) and ",[532,622,623],{},"data-api"," (API origin, for test environments only). Color, position and title come from the workspace configuration.",[626,627,629],"callout",{"type":628},"tip",[504,630,631,632,635],{},"There is a demo page at ",[532,633,634],{},"\u002Fdemo\u002Fwebchat"," where you paste the key and see the widget running.",[513,637,485],{"id":638},"what-the-visitor-sees",[640,641,642,645,656],"ul",{},[520,643,644],{},"A floating button that opens the chat; texts in Portuguese, Spanish or English.",[520,646,647,648,651,652,655],{},"Name and phone are ",[508,649,650],{},"optional"," and ",[508,653,654],{},"not verified",": they do not identify the visitor and are never merged with existing customers.",[520,657,658],{},"The conversation lives in page memory: reloading starts a new one. Nothing is stored in the browser.",[513,660,490],{"id":661},"security",[640,663,664,671,677,680,697,708,711,718],{},[520,665,666,667,670],{},"The publishable key only opens anonymous conversations: it gives ",[508,668,669],{},"no"," access to the dashboard, the private API or customer data. It can be revoked at any time (and rotates when you publish again).",[520,672,673,674,543],{},"The workspace always comes from the key; the widget never sends a ",[532,675,676],{},"workspace_id",[520,678,679],{},"Only allowed domains can use the chat (per-workspace CORS). No cookies.",[520,681,682,689,690,693,694,696],{},[508,683,684,685,688],{},"The ",[532,686,687],{},"Origin"," check is not authentication."," It only protects against other websites in a browser; any non-browser client (a script, ",[532,691,692],{},"curl",") can send whatever ",[532,695,687],{}," it likes. That is why the publishable key is not a secret and the real protection is the usage limits, not the origin.",[520,698,699,700,703,704,707],{},"Cost caps: messages per conversation and per hour, daily messages per workspace, new conversations per hour and simultaneous live connections. When one is exhausted the API answers ",[532,701,702],{},"429"," with ",[532,705,706],{},"Retry-After"," and the assistant is not invoked. Idle conversations expire on their own and are purged, including the visitor's declared name.",[520,709,710],{},"Usage limits per IP, per conversation and per workspace; messages up to 1000 characters.",[520,712,713,714,717],{},"Visitor text is ",[508,715,716],{},"untrusted data"," for the assistant, and the widget renders all text as plain text (never HTML).",[520,719,720],{},"The chat is reactive only: it sends no reminders or proactive messages.",[513,722,48],{"id":723},"api",[504,725,726,727,730,731,612,734,612,737,651,740,743,744,543],{},"Management (owner, panel cookie): ",[532,728,729],{},"GET|PUT|DELETE \u002Fv1\u002Fwebchat-site",". Public: ",[532,732,733],{},"GET \u002Fv1\u002Fpublic\u002Fwebchat\u002Fconfig",[532,735,736],{},"POST \u002Fv1\u002Fpublic\u002Fwebchat\u002Fsessions",[532,738,739],{},"GET|POST \u002Fv1\u002Fpublic\u002Fwebchat\u002Fsessions\u002F{id}\u002Fmessages",[532,741,742],{},"GET \u002Fv1\u002Fpublic\u002Fwebchat\u002Fsessions\u002F{id}\u002Fevents"," (SSE). The contract lives in ",[532,745,746],{},"packages\u002Fopenapi\u002Fopenapi.yaml",[626,748,750],{"type":749},"warning",[504,751,752,753,756],{},"Disabled by default (",[532,754,755],{},"PUBLIC_WEBCHAT_ENABLED=false",") until the security review and explicit activation.",[758,759,760],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}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 .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);}",{"title":557,"searchDepth":90,"depth":762,"links":763},3,[764,765,766,767],{"id":515,"depth":90,"text":480},{"id":638,"depth":90,"text":485},{"id":661,"depth":90,"text":490},{"id":723,"depth":90,"text":48},"Put the Wagend assistant on your site with a single script line, no WhatsApp number needed.","md",{},true,"---\ntitle: WebChat on your website\ndescription: Put the Wagend assistant on your site with a single script line, no WhatsApp number needed.\nbadge: Draft\n---\n\nWebChat is a chat widget your business pastes into its own website. It talks to the **same bot** as WhatsApp (same schedules, prices and bookings) but needs no phone number or third-party approval: ideal for trying things out and as a storefront on your site.\n\n## Install\n\n1. In the dashboard, as **owner**, publish WebChat and list the **allowed domains** (for example `https:\u002F\u002Fwww.myshop.com`). You get a **publishable key** `wg_web_…`.\n2. Paste the snippet before `\u003C\u002Fbody>`:\n\n```html\n\u003Cscript src=\"https:\u002F\u002Fwagend.app\u002Fwidget.js\" data-key=\"wg_web_…\" defer>\u003C\u002Fscript>\n```\n\nOptional attributes: `data-locale` (`pt`, `es` or `en`; defaults to the browser language) and `data-api` (API origin, for test environments only). Color, position and title come from the workspace configuration.\n\n::callout{type=\"tip\"}\nThere is a demo page at `\u002Fdemo\u002Fwebchat` where you paste the key and see the widget running.\n::\n\n## What the visitor sees\n\n- A floating button that opens the chat; texts in Portuguese, Spanish or English.\n- Name and phone are **optional** and **not verified**: they do not identify the visitor and are never merged with existing customers.\n- The conversation lives in page memory: reloading starts a new one. Nothing is stored in the browser.\n\n## Security\n\n- The publishable key only opens anonymous conversations: it gives **no** access to the dashboard, the private API or customer data. It can be revoked at any time (and rotates when you publish again).\n- The workspace always comes from the key; the widget never sends a `workspace_id`.\n- Only allowed domains can use the chat (per-workspace CORS). No cookies.\n- **The `Origin` check is not authentication.** It only protects against other websites in a browser; any non-browser client (a script, `curl`) can send whatever `Origin` it likes. That is why the publishable key is not a secret and the real protection is the usage limits, not the origin.\n- Cost caps: messages per conversation and per hour, daily messages per workspace, new conversations per hour and simultaneous live connections. When one is exhausted the API answers `429` with `Retry-After` and the assistant is not invoked. Idle conversations expire on their own and are purged, including the visitor's declared name.\n- Usage limits per IP, per conversation and per workspace; messages up to 1000 characters.\n- Visitor text is **untrusted data** for the assistant, and the widget renders all text as plain text (never HTML).\n- The chat is reactive only: it sends no reminders or proactive messages.\n\n## API\n\nManagement (owner, panel cookie): `GET|PUT|DELETE \u002Fv1\u002Fwebchat-site`. Public: `GET \u002Fv1\u002Fpublic\u002Fwebchat\u002Fconfig`, `POST \u002Fv1\u002Fpublic\u002Fwebchat\u002Fsessions`, `GET|POST \u002Fv1\u002Fpublic\u002Fwebchat\u002Fsessions\u002F{id}\u002Fmessages` and `GET \u002Fv1\u002Fpublic\u002Fwebchat\u002Fsessions\u002F{id}\u002Fevents` (SSE). The contract lives in `packages\u002Fopenapi\u002Fopenapi.yaml`.\n\n::callout{type=\"warning\"}\nDisabled by default (`PUBLIC_WEBCHAT_ENABLED=false`) until the security review and explicit activation.\n::\n",{"title":77,"description":768},"MXGubEw1x1DL1s3EIDdXBLQ74nXdP_bFLj_Zzk4FtRQ",[776,778],{"title":73,"path":74,"stem":75,"description":777,"children":-1},"How to model a barbershop, an aesthetics clinic, a medical office, a restaurant and deliveries.",null,1790998579332]