AI Workers
The AI gateway workers are hooks that allow you to control the behavior of your resources remotely through events.
With an external controller configured, events are sent to it, and the response from your controller determines whether that action should continue, be aborted, or be configured.
When an event is triggered on the AIVAX side, a POST request is fired to the configured worker with information about the triggered event. Based on its response, the action can be cancelled or configured. There is no caching – the request is made on every event that occurs in your AI gateway.
The processing time of the response adds latency to every gateway action, however, it adds a layer of control and moderation that you can manage at any time.
How it works
When an event is triggered, a POST request is sent to your worker following the format below:
{
"gatewayId": "019a6afb-5a03-7b83-a1a2-760bd1ecd11c",
"moment": "2025-12-29T17:04:39",
"event": {
"name": "message.received",
"data": {
"messages": [
{
"role": "system",
"content": "User local date is Monday, December 29, 2025 (timezone is America/Sao_Paulo)"
},
{
"role": "user",
"content": "bom dia"
},
{
"role": "assistant",
"content": "Bom dia! 😊 Como posso te ajudar hoje?"
},
{
"role": "user",
"content": "tudo bem?"
}
],
"origin": [
"SessionsApi"
],
"externalUserId": "mini-app-session@hse075q0q5gftm6jmitvi5",
"metadata": {}
}
}
}
The example above illustrates a message.received event message with its event arguments.
Response types
After sending the request, AIVAX waits for your worker's response. There are three possible response types:
| Response | Behavior |
|---|---|
| OK response (2xx) | Continues and proceeds with the normal execution of the event. |
| Other responses | Aborts and stops the execution of the event. |
Content-Type application/json+worker-action |
Executes the actions specified in the response and continues execution. |
Events
Currently, the events that can be sent to your worker are:
message.received
Sent when a message is received by the gateway. This event is triggered with the full conversation message history.
{
"name": "message.received",
"data": {
"messages": [],
"origin": ["SessionsApi"],
"externalUserId": "mini-app-session@lot1xc9k03g2my3j4w2y1",
"metadata": {}
}
}
Available actions
To perform actions on the message.received event, return a response with the header Content-Type: application/json+worker-action and a body in the format:
{
"type": "message.received.response",
"data": {
"rewrites": [
// list of actions
]
}
}
The actions available for the rewrites field are:
| Action | Description | Parameters |
|---|---|---|
clear |
Removes all specified entities from the context. | argument: the entity to be removed. Can be messages, meta, system, tools, skills or all. Defaults to all when null. |
add-message |
Adds a message to the context. | message: OpenAI‑compatible message object (e.g., role and content) |
remove-message |
Removes a message from the context by index. | index: index of the message to be removed |
add-system |
Adds a system instruction. | message: instruction text |
add-tool |
Adds a tool to the context. | tool: JSON object of the tool |
add-protocol-tool |
Adds a definition of a protocol function to the context. | tool: protocol function definition object |
Action examples
Clear and add message:
{
"type": "message.received.response",
"data": {
"rewrites": [
{
"type": "clear"
},
{
"type": "add-message",
"message": {
"role": "user",
"content": "Mensagem substituída pelo worker."
}
}
]
}
}
Add system instruction:
{
"type": "message.received.response",
"data": {
"rewrites": [
{
"type": "add-system",
"message": "Responda sempre em português formal."
}
]
}
}
Remove message by index:
{
"type": "message.received.response",
"data": {
"rewrites": [
{
"type": "remove-message",
"index": 0
}
]
}
}
tool.called
Sent when an internal server tool is about to be executed.
{
"name": "tool.called",
"data": {
"toolName": "memory_search",
"toolArguments": {
"query": "preferências do usuário"
},
"origin": "SessionsApi",
"externalUserId": "mini-app-session@lot1xc9k03g2my3j4w2y1",
"metadata": {}
}
}
Available actions
To perform actions on the tool.called event, return a response with the header Content-Type: application/json+worker-action and a body in the format:
{
"type": "tool.called.response",
"data": {
"result": "Resultado textual da ferramenta.",
"messages": []
}
}
data fields:
| Field | Description |
|---|---|
result |
Textual content injected as the tool's result. |
messages |
Optional list of additional OpenAI‑format messages attached to the conversation context. |
When tool.called.response is returned, the result provided by the worker is used instead of the tool's default execution.
Examples
Blocking unauthorized users
The example below illustrates a Cloudflare Worker that authenticates a Telegram conversation based on the username:
export default {
async fetch(request, env, ctx) {
const CHECKING_GATEWAY_ID = "0197dda5-985f-7c76-96e5-0d0451c596e5";
const ALLOWED_USERNAMES = ["myusername"];
if (request.method == "POST") {
const requestData = await request.json();
const { event, gatewayId } = requestData;
if (gatewayId === CHECKING_GATEWAY_ID &&
event.name == "message.received" &&
event.data.externalUserId?.startsWith("zp_telegram:")) {
const telegramUsername = event.data.externalUserId.split(':')[0].split('@')[0];
if (!ALLOWED_USERNAMES.includes(telegramUsername)) {
return new Response("User is not authed", { status: 400 });
}
}
}
return new Response();
}
};
Intercepting and modifying messages
The example below shows how to intercept a message and replace it with another when the user is not authorized:
export default {
async fetch(request, env, ctx) {
const CHECKING_GATEWAY_ID = "0197dda5-985f-7c76-96e5-0d0451c596e5";
if (request.method == "POST") {
const requestData = await request.json();
const { event, gatewayId } = requestData;
if (gatewayId === CHECKING_GATEWAY_ID && event.name == "message.received") {
const userIsPaid = await checkUserSubscription(event.data.externalUserId);
if (!userIsPaid) {
return new Response(JSON.stringify({
type: "message.received.response",
data: {
rewrites: [
{ type: "clear" },
{
type: "add-message",
message: {
role: "user",
content: "O usuário enviou uma mensagem mas ela foi excluída pelo sistema. Informe ao usuário que ele não pagou sua assinatura mensal para continuar."
}
}
]
}
}), {
headers: { "Content-Type": "application/json+worker-action" }
});
}
}
}
return new Response();
}
};
async function checkUserSubscription(externalUserId) {
// Implement your subscription‑checking logic here
return true;
}
Português
English