Actions in your systems
Actions
How Texa AI asks your systems for something, the three ways an action runs, and how to answer.
When Texa AI needs something from your systems, such as a transfer's status, a refund or a balance band, it asks with an action request. The request uses the action's name and typed inputs. What each action is (inputs, outputs, whether it changes something, whether the customer must say yes or be verified first) is set in Texa Desk under Texa AI → Actions. A named person approves it there. Texa AI never invents an action or runs one the procedures do not allow.
There are three ways to run an action. You choose per action.
| Way | Who holds your credentials | You write |
|---|---|---|
| Texa calls your API: a connector in Texa Desk (HTTP, OAuth 2, HMAC, a ready-made one such as Paystack or Flutterwave) | Texa, encrypted | nothing |
| Texa calls your server: a signed webhook connector | you | one endpoint |
| You run it: from the API's answer | you | a few lines around your API calls |
If your systems speak MCP, a connector of type MCP server turns your tools into actions (MCP).
The first two run by themselves. In the API's answer, the action_request event is
marked "handled_by": "texa" with its result's status, and Texa AI's next reply follows it
in the same events. Banks and fintechs that keep credentials inside use the second way,
or the Texa Relay inside their network.
You run it
{"type": "action_request", "request_id": "act_3f9c...", "action": "get_transfer_status",
"inputs": {"reference": "TX-9B2LC"}, "idempotency_key": "idem_act_3f9c...",
"expires_at": "2026-09-27T10:05:00Z"}
Run it, then give the result:
r = texa.post(f"/conversations/{cid}/action-results", json={
"request_id": event["request_id"],
"status": "succeeded", # or "failed"
"data": {"status": "paid_out", "amount": 80000}})
for e in r.json()["events"]: # Texa AI carries on: show its reply
...
- Answer before
expires_at. Texa AI tells the customer it is checking, and past that time it moves on. - A failure is a failure. Send
"status": "failed"with anerror(at most 500 characters, for the log). Texa AI then says it did not work and offers a person. It never claims an action succeeded when it did not. - Nothing found is not a failure. When a lookup finds no record (no transfer with that
reference), send
"status": "not_found"with no data. Texa AI tells the customer no record matches and asks them to check it, instead of saying the check did not work. When Texa calls your API itself, a read's HTTP 404 means the same. - Writes use
idempotency_key. If the same action request arrives twice (a retry, a timeout), your system must do it once. Passidempotency_keyto your payment or ledger API. - Only return the fields the action declares. Anything the action marks as private never reaches the customer.
Texa calls your server (signed webhook)
In Texa Desk, add a connector of type Webhook with your URL. You get a signing secret, shown once. For each request, Texa POSTs:
POST https://your-server/texa/actions
X-Texa-Timestamp: 1790000000
X-Texa-Signature: sha256=<hex HMAC-SHA256 of "1790000000.<raw body>" with your secret>
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": "...", "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/..."}
customer is the person as your Texa Desk contact knows them: the phone a WhatsApp
message came from, the address an email came from. It says who is asking, not that
they proved it. Texa AI checks identity before any action marked for verified
customers only. An action you mark Load at the start (Actions screen) runs
before Texa AI's first reply in every conversation, with inputs empty and this
customer, so look the customer up by phone or email. conversation_id is
null then.
Check the signature and that the timestamp is within 5 minutes, 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 with the same secret.
import hashlib, hmac, time
def signed_by_texa(secret: str, timestamp: str, raw_body: bytes, signature: str) -> bool:
if abs(time.time() - int(timestamp)) > 300:
return False
expected = "sha256=" + hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body,
hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
In the sandbox
With a test key, set what an action answers, including faults, before you build it:
curl -X PUT https://api.texadesk.com/v1/sandbox/actions/get_transfer_status \
-H "Authorization: Bearer $TEXA_TEST_KEY" -H "Content-Type: application/json" \
-d '{"response": {"status": "succeeded", "data": {"status": "paid_out", "amount": 80000}}}'
See the sandbox for scenarios and faults.