---
name: hasos-whatsapp-call
description: Give yourself a WhatsApp voice through Hasos — call the user (reminders, alerts, briefings) or people on their behalf from the user's own WhatsApp number, with a real-time AI voice, let the user call you and ask you to do things, and read every outcome (transcript + summary). Includes a fully guided setup you run in the chat — the user never needs to open the Hasos dashboard. Use when the user wants you to call them, call someone, or be reachable by phone.
---

# Hasos — WhatsApp voice calls for AI agents

With Hasos you can **call the user on WhatsApp**, **call other people for them**, and **answer when they call you** — from their own WhatsApp number, with a real-time voice (OpenAI or xAI, their key). During calls you can **act live**: when they say "post on Slack that I'm late", the request reaches you and you do it.

Base URL: `https://api.hasos.com.br` · Auth header: `x-api-key: <key>` · MCP: `https://api.hasos.com.br/mcp` (same header).

---

## 1. Guided setup — you drive it, the user only answers

Talk to the user in their language, one step at a time. Never ask them to open the dashboard; every step below is an API/MCP call you make.

### 1.1 Connect their Hasos account (no key copying)

```bash
curl -s -X POST https://api.hasos.com.br/v1/agent-auth/start \
  -H "Content-Type: application/json" -d '{"agentName":"<your name, e.g. Grokbot>"}'
# → { deviceCode, userCode, verificationUrl, interval: 5, expiresIn: 900 }
```

Send the user **verificationUrl** and the **userCode** ("open this link, sign in or create your account, check the code is ABCD-2345 and click Authorize"). Then poll every 5 s:

```bash
curl -s -X POST https://api.hasos.com.br/v1/agent-auth/token \
  -H "Content-Type: application/json" -d '{"deviceCode":"<deviceCode>"}'
# pending → keep polling · approved → { apiKey } (returned ONCE — store it securely) · denied/expired → tell the user / start again
```

New accounts need a **business email** (Gmail/Hotmail are rejected) — mention it if signup fails.

### 1.2 Ask Hasos what's next — after every step

`get_setup_status` (MCP) or `GET /v1/agent-setup` returns what is done and **`next: { step, tool, say }`**. Follow `next.say`. Steps, in order:

| next.step | What you do |
|---|---|
| `start_trial` / `subscribe` | `start_trial` (MCP) or `POST /v1/billing/checkout {"plan":"pro","quantity":1,"trial":true}` → send the user the `url`. 5 days free, card required, nothing charged today. |
| `create_device` | Ask for the WhatsApp number they'll connect, then `create_device {phone}` (MCP) or `POST /v1/instances {"phone":"5511999998888"}` → keep the `id`. |
| `pair_device` | `get_pairing_code {deviceId}` (MCP) or `GET /v1/instances/{id}/pairing-code` → an 8-character code. Tell them: *WhatsApp → Linked devices → Link a device → Link with phone number instead → type the code*. Poll `get_device` / `get_setup_status` until `CONNECTED` (if the code expires, ask for a new one). |
| `set_voice` / `fix_voice` | Ask for an **OpenAI** key (platform.openai.com/api-keys — needs API credits; ChatGPT Plus doesn't count) or **xAI** key (console.x.ai). `set_voice {provider, apiKey}` or `PUT /v1/voice-agent`. |
| `set_live_actions` *(optional, recommended)* | Follow **section 3**: run the small endpoint, expose it with ngrok (static domain), `set_live_actions {agentUrl}`, then `test_live_actions` until it says ok. |
| `set_inbound` *(optional)* | Ask for the user's cell phone. `set_inbound {deviceId, allowedNumbers:["+55…"]}` or `PUT /v1/instances/{id}/agent-inbound {"enabled":true,"allowedNumbers":["+55…"]}`. Now they can call that number and talk to you. |
| `ready` | Place a test call (section 2): "call me in 1 minute to test". |

---

## 2. Call someone

**The user's request is the permission.** When the user asks you to call a number ("call Gabs at +55 16…"), call it right away — don't ask "can I call this number?" again, and don't ask per number. Only ask first when calling is *your* idea (someone the user never mentioned).

**Avoid an approval prompt for every number:**
- Prefer the **MCP tool `place_agent_call`** — most agent runtimes let the user approve a tool once ("always allow"), whatever the arguments.
- If you must use the shell, call one **fixed script** (below) so the user allow-lists `hasos-call.sh` once, instead of approving every different `curl`. Ask the user to add it to your command allow-list the first time.

```bash
#!/usr/bin/env bash
# hasos-call.sh <to> <instructions> [firstMessage]   (env: HASOS_API_KEY, HASOS_DEVICE_ID)
set -euo pipefail
jq -n --arg d "$HASOS_DEVICE_ID" --arg to "$1" --arg i "$2" --arg f "${3:-}" \
  '{deviceId:$d, to:$to, instructions:$i, firstMessage:$f}' |
curl -s -X POST https://api.hasos.com.br/v1/calls/agent \
  -H "x-api-key: $HASOS_API_KEY" -H "Content-Type: application/json" -d @-
```

MCP: `place_agent_call` → `get_call`. REST:

```bash
curl -s -X POST https://api.hasos.com.br/v1/calls/agent \
  -H "x-api-key: $HASOS_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "deviceId": "DEVICE_ID",
    "to": "+5511999998888",
    "instructions": "Remind Augusto about the 3pm supplier meeting and ask if he wants to move it.",
    "firstMessage": "Oi, Augusto! Aqui é o seu assistente.",
    "maxDurationSec": 300
  }'
# → {"callId":"…","status":"RINGING"}

curl -s https://api.hasos.com.br/v1/calls/CALL_ID -H "x-api-key: $HASOS_API_KEY"
# poll ~every 10 s: answered, durationSec, endReason, recordingUrl; transcript + summary ~1 min after it ends
# (Pacote IA on Pro, or Plano Agente with an OpenAI voice key; otherwise transcribe recordingUrl yourself — see 2.1)
```

Good `instructions`: the goal, the facts the voice may use, and how to finish. The voice knows only what you write and won't invent. Default language is Brazilian Portuguese (`language` to change). It hangs up by itself after saying goodbye. Ask for **unambiguous** answers in the instructions ("confirm the day and whether it's morning or evening") — the voice already confirms ambiguous replies, but say it when it matters.

### 2.1 Always read the outcome — and transcribe it yourself if needed

A call is only done when you've **acted on what the person said** (add to the calendar, reply to the user, update a task…). After every call:

1. Poll `get_call` until `endedAt` is set. If `answered` is false, tell the user (offer to retry or send a message).
2. If `transcript` is there → use it.
3. If the call ended but there's **no transcript** (`transcriptStatus` pending/failed/`UNAVAILABLE_OR_PENDING`, or a `hint`) → **don't wait and don't stop**: download `recordingUrl` (mp3, temporary link; if it's null, retry `get_call` in ~5 s, or use `get_call_recording`), **transcribe it with your own speech-to-text** (e.g. Whisper), and continue. Do this on any transcript error, without the user asking.
4. Act on the answer. If it's still ambiguous ("today at 10" said at 5 pm), don't guess silently — ask the user, or act on the most likely reading **and say what you assumed** so they can correct it.
5. Report back in one or two sentences: what was said, what you did.

```bash
curl -s https://api.hasos.com.br/v1/calls/CALL_ID -H "x-api-key: $HASOS_API_KEY"
# { answered, durationSec, endReason, transcript|null, summary|null, transcriptStatus, recordingStatus, recordingUrl, hint? }
curl -s -o call.mp3 "<recordingUrl>"   # then transcribe call.mp3 yourself
```

---

## 3. Live actions — act during the call (both directions)

When the person asks for something mid-call ("post on Slack that I'm late", "book John tomorrow at 10", "cancel my 3pm"), Hasos POSTs it to **your** endpoint and speaks your reply. Set this up for the user — it's what makes calls useful.

### 3.1 Run a tiny endpoint next to you

It must: verify the signature, hand `request` to **you** (your own runtime: local API, CLI, task inbox…), and answer within **25 s**. Minimal Node version (no dependencies):

```js
// hasos-actions.mjs — node hasos-actions.mjs   (env: HASOS_SECRET=<agentSecret>)
import http from "node:http";
import crypto from "node:crypto";

const SECRET = process.env.HASOS_SECRET;

// ⬇ Replace with however you (the agent) execute a task and describe the result.
async function handle(request, { direction, from }) {
  return `Feito: ${request}`;
}

http.createServer((req, res) => {
  let raw = "";
  req.on("data", (c) => (raw += c));
  req.on("end", async () => {
    const sig = "sha256=" + crypto.createHmac("sha256", SECRET).update(raw).digest("hex");
    const got = req.headers["x-hasos-signature"] ?? "";
    if (sig.length !== got.length || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(got))) {
      res.writeHead(401).end();
      return;
    }
    const ev = JSON.parse(raw); // { event, direction, callId, from, request, timestamp }
    const reply = await handle(ev.request, ev);
    res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({ reply }));
  });
}).listen(8787, () => console.log("Hasos live actions on :8787"));
```

Python equivalent (Flask): verify `hmac.new(secret, request.get_data(), sha256).hexdigest()` against the `X-Hasos-Signature` header (after `sha256=`), then `return {"reply": result}`.

Long task (> 20 s)? Reply right away with what you started (*"Starting the report — I'll message you when it's done"*) and finish in the background.

### 3.2 Expose it with ngrok (recommended)

Hasos needs a **public HTTPS** URL. If you run on the user's machine, use **ngrok**:

1. Ask the user to create a free account at ngrok.com and give you the **authtoken** (dashboard.ngrok.com → Your Authtoken) — or have them run the commands.
2. Install and authenticate: `brew install ngrok` (macOS) / `npm i -g ngrok` · `ngrok config add-authtoken <token>`.
3. Claim the **free static domain** (dashboard.ngrok.com → Domains) so the URL never changes, then:
   `ngrok http 8787 --url=<your-domain>.ngrok-free.app`
4. Keep both the endpoint and ngrok running (a background service, `pm2`, `launchd`, `systemd`…). Without a static domain the URL changes on every restart — you'd have to register it again.

Alternatives: Cloudflare Tunnel (`cloudflared tunnel --url http://localhost:8787`) or any HTTPS server you already have. Local IPs, `localhost` and plain `http://` are rejected.

### 3.3 Register and test

1. `set_live_actions {"agentUrl":"https://<your-domain>.ngrok-free.app"}` (or `PUT /v1/voice-agent/live-actions`) → returns **`agentSecret`**: put it in `HASOS_SECRET` and restart the endpoint.
2. `test_live_actions` (or `POST /v1/voice-agent/live-actions/test`) → Hasos sends a signed test request (`"direction":"test"`) and shows your reply or the exact error (tunnel down, timeout, 401 bad signature…). Fix until `ok: true`.
3. Tell the user: *"Ready — during our calls, ask me to do things and I'll do them."*

Request format, for reference:

```http
POST <agentUrl>
X-Hasos-Signature: sha256=<hex HMAC-SHA256 of the raw body with agentSecret>
{"event":"agent_request","direction":"outbound|inbound|test","callId":"…","from":"5511999998888","request":"Book a meeting with John tomorrow at 10","timestamp":"…"}
```

Reply `{"reply":"Done — meeting with John tomorrow 10am."}` (plain text also works). `direction: "inbound"` means the user called you.

---

## 4. Errors

- `test_live_actions` fails → tunnel down (restart ngrok), wrong URL (re-run set_live_actions), 401 (secret mismatch — use the latest agentSecret), timeout (reply faster).
- `VOICE_NOT_CONFIGURED` → set_voice. If `get_setup_status` shows `voice.lastError` (no credits, invalid key), relay it — the user must fix it at the provider.
- `NOT_CONNECTED` → the number is offline; re-pair (1.2 `pair_device`).
- `TRIAL_REQUIRED` / `ACCOUNT_SUSPENDED` / `SUBSCRIPTION_REQUIRED` → `start_trial` link.
- `PACED_TOO_FAST` / `PACED_DAILY_CAP` / `QUOTA_EXCEEDED` → wait and retry later.

## 5. Etiquette

Call whoever the user asks you to — no extra confirmation needed. Don't cold-call people the user never mentioned, and never spam or mass-dial: WhatsApp can restrict the user's number. Hasos already paces calls from each number for you.
