# Chatbot ▸ Advanced — integrations guide

Written for: developers connecting their own systems to a LegoTech chatbot. Published at https://app.legotechapps.com/developers/chatbot/

Base URL: `https://legotechapps.com/lt/api`

## 1. The bug loop (end to end)

1. **Dashboard → Chatbot → Advanced → Actions → "Report a bug".** Point it at your server URL (or pick a GitHub repository). Switch it on.
2. A customer writes *"the app crashes when I open the cart"*. The bot collects the fields you defined (description, steps, device…) and replies with a case number, e.g. `C-48213`.
3. Your server receives a signed `POST`:

```json
{
  "id": "6f1c…", "type": "action.called", "created_at": "2026-11-01T10:00:00+00:00",
  "business_id": 5, "bot": { "id": 74, "name": "Support" },
  "data": {
    "ref": "C-48213", "action": "report_bug", "title": "Report a bug",
    "input": { "description": "The app closes when I open the cart", "device": "iPhone 15, 2.3.1" },
    "conversation_id": 1234, "channel": "whatsapp",
    "reply_url": "https://legotechapps.com/lt/api/v1/cases/C-48213/reply",
    "status_url": "https://legotechapps.com/lt/api/v1/cases/C-48213/status"
  }
}
```

4. Verify the signature, then hand the report to whatever fixes things — a ticket queue, or a coding agent (e.g. Claude Code in GitHub Actions opening a PR). **Keep a person approving the PR before it ships.**
5. When the fix is out, answer the customer:

```bash
curl -X POST https://legotechapps.com/lt/api/v1/cases/C-48213/reply \
  -H "Authorization: Bearer ltk_…" -H "Content-Type: application/json" \
  -d '{"text": "Fixed in version 2.3.2 — please update the app."}'
```

The bot rewrites it in the customer's language (send `"verbatim": true` to skip that). Depending on the action's setting the answer either goes straight to the customer, or waits under **Approvals** for the owner (who may edit it). Response `200` = sent, `202` = waiting for approval.

**WhatsApp's 24-hour rule:** a free-form message cannot reach someone who last wrote more than 24 hours ago. Then the answer goes out inside the business's approved `order_update` template (case number + your text) — if that template is approved and the business allows first messages. Otherwise it is not sent, the case says so, and the owner gets an alert with your text. `GET /v1/cases/{ref}` shows `can_reply_now` (inside the 24 hours).

**Customers can ask.** "What is happening with C-48213?" — the bot answers from the case's status and last *sent* answer (`lookup_case`), only within the conversation that opened it.

## 2. Verifying signatures (Standard Webhooks)

Headers: `webhook-id`, `webhook-timestamp`, `webhook-signature: v1,<base64>`; secret format `whsec_…`.
Signature = `base64(HMAC_SHA256(base64decode(secret without "whsec_"), "{id}.{timestamp}.{raw body}"))`. Reject timestamps older than 5 minutes. Any Standard Webhooks library works:

```js
import { Webhook } from "standardwebhooks";
const event = new Webhook(process.env.LEGOTECH_SECRET).verify(rawBody, req.headers);
```

```php
$key = base64_decode(substr($secret, 6));
$expected = 'v1,' . base64_encode(hash_hmac('sha256', "$id.$ts.$raw", $key, true));
```

Retries: 1 min, 5 min, 30 min, 2 h, 6 h, 24 h, then the delivery is marked dead (the dashboard log has "send again"). `webhook-id` is stable across retries — dedupe on it.

## 3. Wait mode

An action in **wait** mode POSTs the same body and waits up to its timeout (≤10 s). Answer with JSON; `reply` / `message` / `answer` / `text` is what the bot tells the customer (as data, never as instructions). Optional `status` (`answered`, `closed`…) and `external_ref`. A timeout or error falls back to queue mode — the customer still gets a case number.

## 4. API reference (`Authorization: Bearer ltk_…`)

| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | `/v1/me` | any | Which business this key belongs to (Zapier auth test) |
| GET | `/v1/cases/{ref}` | cases | Case details and status |
| POST | `/v1/cases/{ref}/reply` | cases | `{text, verbatim?, status?, external_ref?, external_url?}` |
| POST | `/v1/cases/{ref}/status` | cases | `{status: open\|waiting\|answered\|closed, note?, notify?}` |
| POST | `/v1/hooks` | hooks | Subscribe `{url, event\|events}` → `{id, secret}` (Zapier REST hooks) |
| DELETE | `/v1/hooks/{id}` | hooks | Unsubscribe |
| GET | `/v1/samples/{event}` | hooks | Sample payload (a list) |
| POST | `/v1/datasources/{id}/rows` | data | Push rows into a "your system sends the data" source: `{rows: [...], replace?: true}` (≤256 KB) |
| PUT | `/v1/knowledge/{key}` | knowledge | Upsert a knowledge entry `{title, body, bot_id?}` (no bot_id = every bot) |
| DELETE | `/v1/knowledge/{key}` | knowledge | Remove it |

Rate limit: 120 requests a minute per key. Keys are created by the business owner under Advanced → API & cases and shown once. A key can be limited to **allowed addresses** (IPs or CIDR ranges, e.g. `203.0.113.0/24`); from anywhere else it answers `403 IP_NOT_ALLOWED`. Every key, webhook, action and source change is listed under **Activity** (who, when, from which IP).

## 5. Events

`lead.captured`, `handoff.requested`, `alert.raised`, `gap.noted`, `booking.created`, `order.created`, `action.called`, `case.updated` — and, only when picked, `message.received`, `message.sent`. Customer name/phone/email are included only when the subscription (or action) has "Include the customer's details" on.

## 6. GitHub target

Action target **GitHub repository** opens an issue per case (fine-grained token with Issues read/write on that repo). Add the webhook shown on the action card to the repo (events: Issues, Issue comments, Releases). Then:
- comment `/reply <text>` on the issue → the customer gets it;
- close the issue (completed) → "it has been fixed";
- publish a release mentioning `C-48213` → "fixed in version X".

## 7. Live data

Advanced → Live data. Three modes per source:
- **copy** (default): we read your JSON or CSV URL on a timer (5 min – daily) and search the copy; nothing the customer types leaves us. A Google Sheet published as CSV works.
- **real time**: your URL contains `{query}`; the bot calls it with the customer's search words (5 s timeout, falls back to the copy).
- **your system sends the data**: `POST /v1/datasources/{id}/rows` — for a database behind a firewall. We never connect to your database.
