# Run actions on your server

Texa sends each action request to your server, signed, over a webhook or a socket your server opens. Your credentials never leave you.


When you'd rather keep your credentials and logic on your side, Texa sends each action request to
your server and you answer with the result. There are two ways to receive them.

| | Signed webhook | Socket |
|---|---|---|
| Texa connects to | your `https` URL | nothing: your server connects to Texa |
| Needs an inbound port | yes | no |
| Good for | a public API server | a server behind a firewall, a laptop while you build |

Both are set up in Texa Desk under **Texa AI → Actions → Connectors → Your own system**.

## Signed webhook

Texa POSTs each request, signed with the connector's secret:

```http
POST https://your-server/texa/actions
X-Texa-Timestamp: 1790000000
X-Texa-Signature: sha256=<hex HMAC-SHA256 of "1790000000.<raw body>">
X-Texa-Request-Id: act_3f9c...

{"request_id": "act_3f9c...", "action": "get_transfer_status",
 "inputs": {"reference": "TX-9B2LC"}, "idempotency_key": "idem_act_3f9c...",
 "expires_at": "2026-10-19T10:05:00Z", "conversation_id": "cnv_...", "environment": "live",
 "customer": {"ref": "cw:12:345", "name": "Ngozi Eze", "phone": "+2348031234567", "email": null},
 "reply_url": "https://hooks.texadesk.com/actions/results/..."}
```

[Check the signature](/authentication#checking-what-texa-sends-you), then either:

- answer **200** with `{"status": "succeeded", "data": {...}}`, `{"status": "not_found"}` or
  `{"status": "failed", "error": "..."}` within the action's timeout, or
- answer **202**, and later POST the same body to `reply_url`, signed the same way.

`customer` says who is asking as Texa Desk knows them (the phone a WhatsApp message came from), not
that they proved it. Texa AI checks identity first for actions marked for verified customers only.
A request from your website or app chat also says `"signed_in": true` when your server signed the
customer in.

## Socket

Your server opens one WebSocket to Texa and keeps it open; requests come down it.

```text
wss://hooks.texadesk.com/actions/connect
Authorization: Bearer <the connector's socket key, shown once>
```

| Direction | Message |
|---|---|
| Texa → you | `{"type": "ready"}` once connected |
| Texa → you | `{"type": "action_request", "reply_token", "request_id", "action", "inputs", "idempotency_key", "expires_at", "conversation_id"}` |
| you → Texa | `{"type": "action_result", "reply_token", "status", "data", "error"}` |
| you → Texa | `{"type": "ping"}`, answered `{"type": "pong"}` |

```python
# pip install websockets
import asyncio, json, os, websockets

async def main():
    async with websockets.connect("wss://hooks.texadesk.com/actions/connect",
            additional_headers={"Authorization": f"Bearer {os.environ['TEXA_SOCKET_KEY']}"}) as ws:
        async for raw in ws:
            msg = json.loads(raw)
            if msg["type"] != "action_request":
                continue
            data = await run(msg["action"], msg["inputs"], msg["idempotency_key"])
            await ws.send(json.dumps({"type": "action_result", "reply_token": msg["reply_token"],
                                      "status": "succeeded", "data": data}))

asyncio.run(main())
```

Reconnect when the socket drops (with a pause that grows). A request with no socket connected, or no
answer before `expires_at`, fails, and Texa AI tells the customer honestly that it couldn't check.

## Or run it from the API's answer

If your own server is already the one calling the Texa API, you don't need either: action
requests come back in the answer's `events`, and you answer with
`POST /v1/conversations/{id}/action-results`. See [actions](/guides/actions#you-run-it).
