Hand tickets to your agents
Ayven hands a ticket to an agent you already run, such as a coding agent, a script behind a URL or an app in your tracker, the way she hands one to a teammate. You register the agent once and write down when it gets work and what it does when it is called. When an approved ticket is assigned to it and moved to In Progress, the agent gets the ticket.
How it works
An agent is an assignee like any person on your team. Ayven's ticket agent reads Give it work when to decide which tickets go to it, and you can assign one to it yourself from the ticket's assignee picker, where agents are listed under Agents. How the agent then gets the ticket depends on where the ticket lives:
| The ticket is on | Who calls the agent |
|---|---|
| Ayven's own board (native tickets) | Ayven sends the agent's webhook a signed POST with the ticket and its instructions. |
| Jira or YouTrack | Your tracker's own automation runs the agent when it is the assignee. Ayven assigns the ticket and makes no call of her own. |
Each agent's row says which applies: Ayven calls it, Assignee in its own tracker, both, or Nothing reaches it yet (switched on, but with no webhook and no tracker of its own).
Add an agent
-
Open Integrations → Agents
In the Ayven web app, go to Integrations → Agents. Apps Ayven found in Slack, Teams, Jira and YouTrack are already listed, switched off. Search and sort the list as you would Team Access.
-
Add your agent, or switch one on
For an agent with no tracker of its own, choose Add an agent (owners and admins only) and fill in the form. For an app already in the list, switch it on and fill in the same fields on its row. Each field is described under The fields.
-
Copy the signing secret
Saving a webhook for the first time shows a signing secret once. Copy it into your agent so it can check that each call came from Ayven (see Answer a call). It cannot be shown again.
-
Verify the webhook
Press Verify on its row. Ayven sends your webhook a one-off test call, and any
2xxanswer passes it (see The set-up check). The row then reads Webhook verified. Ayven hands no ticket to a webhook that has not passed this check, and changing the URL, the headers or the secret means verifying again.
The fields
| Field | What it is |
|---|---|
| Name | How the agent appears in assignee pickers and on tickets. |
| Webhook URL | Where Ayven sends the ticket. Must be https on a public hostname. Only needed for tickets on Ayven's own board. |
| Headers | Optional. Up to 10 headers sent with every call, such as Authorization: Bearer …, for an endpoint that needs its own key. |
| What to do when it's called | The agent's standing instructions, sent with every ticket. The ticket says what to do this time; this says how the agent handles tickets in general. Example: Open a pull request that does the ticket and link it in a comment. |
| Give it work when | When this agent is the right choice. Ayven reads it when deciding who should get a ticket; it is never sent to the agent. Example: a code change is fully specified. |
When a ticket is handed over
On Ayven's own board, a ticket goes to the agent when all four of these are true:
- Approved. The ticket was filed through an operation someone approved, or a person made it on the board. A ticket Ayven drafted that nobody approved is never handed over.
- Assigned to the agent. The agent is switched on and has a webhook.
- Verified. The agent's webhook has passed the set-up check.
- In Progress. The ticket moves into In Progress, or is assigned to the agent while it is already there.
Approval alone never sends anything; the move or the assignment does. Each move sends exactly one call. A ticket moved out of In Progress and back in is handed over again. The ticket's detail panel ticks off the four conditions and shows Handed to <agent> with the time once it has gone, then Received once your agent has acknowledged the call, or Not received yet until it does.
What the agent receives
POST <your webhook URL>
Content-Type: application/json
X-Ayven-Timestamp: 1793714400
X-Ayven-Signature: sha256=<hex HMAC-SHA256 of "<timestamp>." + the raw body>
X-Ayven-Delivery: dr_…
Authorization: Bearer … (any headers you added)
{
"run_id": "dr_…",
"delegate": {"name": "Coder"},
"instructions": "Open a pull request that does the ticket and link it in a comment.",
"ticket": {
"key": "WEB-14",
"summary": "Add CSV export to the reports page",
"description": "…",
"url": "https://…/workspace?section=tickets&key=WEB-14",
"state": "in_progress",
"priority": "high",
"due": "2026-11-03"
},
"report": {
"instructions": "Any 2xx answer to this call tells Ayven WEB-14 arrived. To move WEB-14 forward, report on your own connection, with report.token as the bearer token: …",
"token": "dt_dr_…",
"mcp_url": "https://platform-gateway-3124114101.europe-west1.run.app/api/v1/mcp/delegations",
"api_url": "https://platform-gateway-3124114101.europe-west1.run.app/api/v1/api-channel/delegations"
}
}description, priority and due may be null. X-Ayven-Delivery equals run_id, one per hand-over. report tells your agent how to report back if it chooses to, in words a model can follow, and carries the token it reports back with.
Answer a call
Any 2xx answer within 10 seconds means the call was received; Ayven never reads the body of your answer. Start the work in the background rather than before you reply.
Every call is also signed with your signing secret. Checking it is up to you, and we recommend it: anyone who learns your URL can post to it. Before acting on a call, your agent can:
- Check the signature. Compute the HMAC-SHA256, with your signing secret, of
X-Ayven-Timestamp, a full stop, and the exact request body bytes. Prefixsha256=and compare it toX-Ayven-Signaturein constant time. Reject anything that does not match. This is the scheme Stripe webhooks use. - Check the time. Reject a call whose
X-Ayven-Timestampis more than 5 minutes old. The timestamp is inside the signature, so it cannot be changed without breaking it. - Check it is new. Reject a
run_idyou have already taken. Ayven sends each hand-over once, so a second call with the samerun_idis a copy of the first.
import hashlib, hmac, json, time
SECRET = "<your signing secret>"
seen_run_ids = set() # keep these where they survive a restart
def sign(message: bytes) -> str:
return "sha256=" + hmac.new(SECRET.encode(), message, hashlib.sha256).hexdigest()
def handle(headers: dict, raw_body: bytes) -> int:
"""One POST from Ayven -> the status to answer with."""
timestamp = headers.get("X-Ayven-Timestamp", "")
expected = sign(f"{timestamp}.".encode() + raw_body)
presented = headers.get("X-Ayven-Signature", "")
if not hmac.compare_digest(expected.encode(), presented.encode()):
return 401 # not from Ayven
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
return 401 # more than 5 minutes off
body = json.loads(raw_body)
if body.get("type") == "endpoint_check": # the set-up check
return 200
run_id = body["run_id"]
if run_id in seen_run_ids:
return 409 # a copy of a call already taken
seen_run_ids.add(run_id)
start_work_in_background(body) # your agent; keep body["report"]["token"]
return 202A call answered 2xx shows Received on the ticket. Any other answer, including none at all, shows Not received yet. Ayven keeps no status code, error or timing from your endpoint, so check your own logs for why.
To get a new secret, clear the webhook URL, save, set it again, and verify again. The old secret stops working at once.
The set-up check
When you press Verify, Ayven sends your webhook a signed POST, with the same three headers as a hand-over, carrying:
{"type": "endpoint_check"}Any 2xx answer passes it. The page tells you only whether the check passed. If it did not, make sure the URL is public and accepts a POST, then verify again.
Report back
Reporting back is optional. A ticket moves forward only when your agent reports, so an agent that never reports leaves its ticket In Progress until someone moves it. Your agent reports on its own connection to Ayven, with the token the call carried in report.token. It needs nothing else: no Ayven account and no API key.
- The token opens one ticket. It reads and moves only the ticket it came with, and only while that ticket stays assigned to your agent and In Progress. It stops working when the hand-over ends, and after 14 days at most. Every refused token gets the same
401. - Send it as a bearer token:
Authorization: Bearer <report.token>. - No call names the ticket. The token already says which one it is.
Over MCP, connect to report.mcp_url (https://platform-gateway-3124114101.europe-west1.run.app/api/v1/mcp/delegations), which has two tools:
| Tool | What it does |
|---|---|
get_ticket | Reads the ticket: summary, description, state, priority, due date and link, the states it can move to, and your agent's last proposed move with its outcome. Takes no arguments. |
move_ticket | Moves the ticket to another state, one of those get_ticket lists. Takes target_state, reason and an optional comment, each of reason and comment at most 2,000 characters. |
Over HTTP, under report.api_url:
GET https://platform-gateway-3124114101.europe-west1.run.app/api/v1/api-channel/delegations/ticket
POST https://platform-gateway-3124114101.europe-west1.run.app/api/v1/api-channel/delegations/ticket/state/propose
{"target_state": "In Review", "reason": "Pull request opened.", "comment": "See #482."}Nothing moves on the call. The move waits for approval like any change Ayven makes, labelled as coming from your agent, and its comment is posted on the ticket under your agent's name, not Ayven's. It is approved by the person who approved the ticket; if a rule approved it, by the person who made it on the board; otherwise by the agent's owner. A ticket that lives in Jira or YouTrack is moved in that tracker directly.
What became of the move. get_ticket shows your agent's last proposed move as waiting for approval, approved, or rejected with the approver's reason. A rejected move leaves the ticket handed to your agent, so it can read the reason and propose again. An approved move that takes the ticket out of In Progress ends the hand-over, so the token then gets 401: to your agent, that 401 means its move landed.
What Ayven keeps hidden, and what she never reads
- The URL and headers are write-only. After saving, the page shows the URL only up to its last
/, and shows header names only. The rest of the path, the query string and every header value are stored with the signing secret and never shown again, to anyone. Edit the visible part and the hidden rest is kept; delete into the dots and it is dropped. - Ayven keeps only whether the answer was a
2xx. Ayven sends the POST and does not follow redirects. She never reads the body of the answer and never reports back a status code, an error or a timing, and the set-up check takes the same time whatever happens, so a webhook cannot be used to probe what Ayven's network can reach. - Only public destinations. IP addresses,
localhost, internal hostnames and cloud metadata addresses are refused when you save, and again when the call is sent.
2xx, never why it did not. Log X-Ayven-Delivery in your agent to see each call arrive and what it answered.Jira and YouTrack
An app found in your Jira or YouTrack, such as Automation for Jira or a YouTrack workflow user, starts switched off. Switch it on and write Give it work when. Ayven can then assign tickets to it, and your tracker's own automation rule runs it as it does today. Ayven does not call it as well. Your tracker's rules run whether or not Ayven approved the ticket, so keep any approval step you need inside the rule itself.
Switch off or delete an agent
Switching an agent off stops Ayven giving it work and frees a place on your plan; its settings stay. An agent you added by hand can also be deleted from its row: every ticket assigned to it becomes unassigned first, then its webhook, headers and signing secret are removed with it, and any token it was given stops working. An app found in Slack, Teams, Jira or YouTrack can only be switched off, because the next scan of those tools would find it again.
Plan limits
A plan limits how many agents can be switched on at once:
| Plan | Agents switched on |
|---|---|
| Free | 3 |
| Standard | 30 |
| Premium | 30 |
| Enterprise | Unlimited |
The Agents page shows how many you have switched on. At the limit, switching another on is refused until you turn one off or move to a bigger plan. Turning one off always works. After a downgrade, agents already on stay on.