Webhook (A2A)
Invoke agents over HTTP using the A2A protocol
Webhook (A2A) lets external systems invoke an agent by POSTing to a per-agent URL. The endpoint follows the A2A (Agent-to-Agent) 1.0 protocol for interoperability with other A2A-compatible callers.
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /v2/a2a/{agentId}/.well-known/agent-card.json | A2A 1.0 AgentCard for capability discovery |
POST | /v2/a2a/{agentId} | JSON-RPC entry point for SendMessage and GetTask |
The AgentCard advertises the agent's name, description, and a single skill derived from the agent. A2A clients fetch it first to discover what the agent can do, then send messages to the POST endpoint.
SDKs
| SDK | A2A 1.0 support | Works with /v2/a2a |
|---|---|---|
| a2a-python | Yes | Yes — use directly |
| a2a-js (TypeScript) | Not yet — tracked in a2a-js#321 | No — speak JSON-RPC directly, or translate role / state enums between 0.3 and 1.0 |
Other languages can call the JSON-RPC endpoint directly using the request shapes below.
Authentication
Both endpoints require an Archestra token in the Authorization header:
Authorization: Bearer <platform_token>
A personal token from Settings > Your Account, a team token from Settings > Teams, or the organization token from Settings > Organization all work, as long as the token has access to the target agent.
SendMessage
JSON-RPC method SendMessage runs a message against the agent.
{
"jsonrpc": "2.0",
"id": 1,
"method": "SendMessage",
"params": {
"message": {
"messageId": "11111111-1111-1111-1111-111111111111",
"role": "ROLE_USER",
"parts": [{ "text": "Summarize the last 5 PRs in repo X." }]
}
}
}
Field notes:
messageId— required, must be unique per message (UUIDs recommended).role—ROLE_USERfor caller,ROLE_AGENTfor the agent's reply.parts[].text— message body.contextId/taskId— omit on the first message; copy from the response for follow-up turns.
The response is one of two shapes inside result:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"message": {
"messageId": "...",
"role": "ROLE_AGENT",
"contextId": "327a5306-c7dc-4e0c-ba2f-107da6c2548b",
"parts": [{ "text": "Here is the summary..." }]
}
}
}
If the agent needs human approval before running a tool, result contains a task with status.state = "TASK_STATE_INPUT_REQUIRED" and metadata.approvalRequests. See Approvals.
Multi-turn conversations
To keep messages in the same conversation, copy contextId from the first response into every subsequent request:
# Turn 1
curl -X POST https://archestra.example.com/v2/a2a/<agentId> \
-H "Authorization: Bearer <platform_token>" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "SendMessage",
"params": {
"message": {
"messageId": "11111111-1111-1111-1111-111111111111",
"role": "ROLE_USER",
"parts": [{ "text": "hi, my name is victor" }]
}
}
}'
# → result.message.contextId = "327a5306-..."
# Turn 2 — reuse contextId
curl -X POST https://archestra.example.com/v2/a2a/<agentId> \
-H "Authorization: Bearer <platform_token>" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "SendMessage",
"params": {
"message": {
"messageId": "22222222-2222-2222-2222-222222222222",
"role": "ROLE_USER",
"contextId": "327a5306-c7dc-4e0c-ba2f-107da6c2548b",
"parts": [{ "text": "do you know who i am?" }]
}
}
}'
contextId is generated by Archestra on the first message. Clients cannot supply their own.
X-Archestra-Session-Id and Mcp-Session-Id do not group conversations — they are observability-only headers. Use contextId to continue a conversation.
Approvals
When an agent's tool call hits a tool invocation policy requiring approval, the response is a task, not a message:
{
"result": {
"task": {
"id": "task-...",
"contextId": "ctx-...",
"status": { "state": "TASK_STATE_INPUT_REQUIRED" },
"metadata": {
"approvalRequests": [
{ "approvalId": "appr-...", "toolName": "send_email", "approved": false, "resolved": false }
]
}
}
}
}
To approve (or reject), send a follow-up SendMessage with taskId, contextId, and decisions in metadata.taskOps:
{
"jsonrpc": "2.0",
"id": 3,
"method": "SendMessage",
"params": {
"message": {
"messageId": "33333333-3333-3333-3333-333333333333",
"role": "ROLE_USER",
"taskId": "task-...",
"contextId": "ctx-...",
"parts": [],
"metadata": {
"taskOps": {
"approvalDecisions": [{ "approvalId": "appr-...", "approved": true }]
}
}
}
}
}
Approvals also work through Slack/Teams ChatOps. The same flow handles multi-request and multi-turn approvals.
GetTask
Use GetTask to fetch the current state of a task (useful while polling an approval task):
{
"jsonrpc": "2.0",
"id": 4,
"method": "GetTask",
"params": { "id": "task-..." }
}
Pass-through payload (v1 only)
The legacy POST /v1/a2a/{agentId} endpoint accepts any non-A2A JSON body. The body is stringified and passed to the agent as the user message — useful for tools like Zapier that just want to fire an event at an agent:
{
"event": "issue_opened",
"title": "Login button broken on Safari",
"url": "https://github.com/acme/app/issues/1421"
}
v1 is single-turn — every call is a fresh conversation. For multi-turn use v2 with a SendMessage envelope.
Observability
Pass a session ID to group all LLM and MCP tool calls in Observability:
X-Archestra-Session-Id: my-session-123
Without it, Archestra generates one per request. The header is independent of contextId — it tags traces only.
Configuration
A2A uses the same LLM configuration as Chat. See Deployment - Environment Variables for the full list of ARCHESTRA_CHAT_* variables.
