Skip to content

trading_agent.gateway.routes.webhook_routes

The incoming webhook: POST /hooks/wake/ wakes a desk with a message.

It lets TradingView alerts, or the user's own scripts, wake a desk. The body is the message: plain text, or JSON whose "text" or "message" field is used, and anything else as JSON text. The webhook token goes in a token query parameter, because TradingView cannot set headers, or in an X-Webhook-Token header. It is a separate token from the gateway's, so pasting it into another service never exposes the token that controls the gateway. Without a webhook token in .env, the route answers 404, as if it did not exist.

Typical usage example:

application.include_router(WebhookRoutes(desks, secrets.webhook_token).router())

WebhookRoutes

WebhookRoutes(desks, token)

Builds the /hooks/wake route.

Creates the routes.

Parameters:

Name Type Description Default
desks Mapping[str, DeskRuntime]

The running desks, keyed by name.

required
token str

The webhook token, or an empty string to switch webhooks off.

required
Source code in src/trading_agent/gateway/routes/webhook_routes.py
def __init__(
    self,
    desks: Mapping[str, desk_runtime.DeskRuntime],
    token: str,
):
    """Creates the routes.

    Args:
        desks (Mapping[str, desk_runtime.DeskRuntime]): The running desks, keyed by name.
        token (str): The webhook token, or an empty string to switch webhooks off.
    """
    self._desks = desks
    self._token = token

router

router()

Builds the router.

Returns:

Type Description
APIRouter

The router holding the webhook route.

Source code in src/trading_agent/gateway/routes/webhook_routes.py
def router(self) -> fastapi.APIRouter:
    """Builds the router.

    Returns:
        fastapi.APIRouter: The router holding the webhook route.
    """
    router = fastapi.APIRouter()
    router.add_api_route(
        '/hooks/wake/{desk_name}',
        self.wake,
        methods=['POST'],
        status_code=202,
    )
    return router

wake async

wake(desk_name, request, token='')

Queues the webhook's message for a desk.

Parameters:

Name Type Description Default
desk_name str

The desk to wake.

required
request Request

The request, for its body and headers.

required
token str

The webhook token from the query string, if given there.

''

Returns:

Type Description
dict[str, Any]

The new trigger's identifier.

Raises:

Type Description
HTTPException

Webhooks are off (404), the token is wrong (401), the desk does not exist (404), or the body is empty (400).

Source code in src/trading_agent/gateway/routes/webhook_routes.py
async def wake(
    self,
    desk_name: str,
    request: fastapi.Request,
    token: str = '',
) -> dict[str, Any]:
    """Queues the webhook's message for a desk.

    Args:
        desk_name (str): The desk to wake.
        request (fastapi.Request): The request, for its body and headers.
        token (str): The webhook token from the query string, if given there.

    Returns:
        dict[str, Any]: The new trigger's identifier.

    Raises:
        fastapi.HTTPException: Webhooks are off (404), the token is wrong (401), the desk does not exist (404), or the body is empty (400).
    """
    if not self._token:
        raise fastapi.HTTPException(status_code=404, detail='Not Found')
    presented = token or request.headers.get('x-webhook-token', '')
    if not hmac.compare_digest(presented.encode(), self._token.encode()):
        raise fastapi.HTTPException(
            status_code=401, detail='Wrong webhook token'
        )
    desk = self._desks.get(desk_name)
    if desk is None:
        raise fastapi.HTTPException(
            status_code=404, detail=f'No desk named {desk_name!r}'
        )
    text = self._message((await request.body()).decode(errors='replace'))
    if not text:
        raise fastapi.HTTPException(
            status_code=400, detail='The message is empty'
        )
    trigger = trigger_module.Trigger.now(
        trigger_module.TriggerSource.WEBHOOK,
        desk_name,
        '\n'.join(
            [
                '[Webhook message. It came from outside trading-agent, from whoever holds the webhook token, such as a TradingView alert; check it against your rules and strategies before acting on it.]',
                text,
            ]
        ),
    )
    desk.submit(trigger)
    return {
        'trigger_id': trigger.trigger_id,
    }