Webhooks: post each new incoming message (closes #7)
check / check (push) Successful in 1m45s
check / check (push) Successful in 1m45s
Each message a contact sends in a direct chat is posted to each webhook on that chat, as JSON holding `chat_id` and the message record of the messages endpoint. The event handler only queues it: `api.Deliveries` serves a queue of 100 with four goroutines, reads the chat's webhooks there, and makes one POST per webhook, with a 10-second timeout, no retry and no redirect followed. A full queue or a failed post is logged by ids, never by URL or text. `bot.Run` stops it last, once the chat client has exited. Tests cover the payload and its routing, a full queue, failures, and a webhook that never answers; the README documents the payload and what delivery promises. Model: opus-5-5
This commit is contained in:
@@ -226,7 +226,9 @@ Nothing is sent, and the answer is:
|
||||
Registers the `url` in the body as a webhook on the chat `id`, and
|
||||
answers `201` with the webhook. If the chat already has a webhook with
|
||||
the same `url`, character for character, the answer is `200` with that
|
||||
webhook instead.
|
||||
webhook instead. Each new message the contact sends in the chat is then
|
||||
posted to the webhook, as
|
||||
[Messages posted to webhooks](#messages-posted-to-webhooks) describes.
|
||||
|
||||
```sh
|
||||
curl -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
|
||||
@@ -252,8 +254,7 @@ The bot keeps the webhooks in `webhooks.json` in `DATA_DIR`, with mode
|
||||
0600, and reads that file at startup, so they survive a restart. Without
|
||||
the file there are none; a file that cannot be read, or that holds
|
||||
anything but webhooks as the bot writes them, aborts startup, as
|
||||
configuration that cannot be parsed does. The bot does not post anything
|
||||
to a webhook yet.
|
||||
configuration that cannot be parsed does.
|
||||
|
||||
Nothing is registered, and the answer is:
|
||||
|
||||
@@ -303,6 +304,51 @@ Nothing is removed, and the answer is:
|
||||
webhook `webhook_id`;
|
||||
- `500` if `webhooks.json` cannot be written.
|
||||
|
||||
### Messages posted to webhooks
|
||||
|
||||
Each new message a contact sends in a chat is posted to each of the
|
||||
chat's webhooks: a `POST` to its `url`, with
|
||||
`Content-Type: application/json`, whose body is the chat's `id` as
|
||||
`chat_id` and the message as `GET /api/v1/chats/{id}/messages` shows it:
|
||||
|
||||
```json
|
||||
{
|
||||
"chat_id": 3,
|
||||
"message": {
|
||||
"id": 9,
|
||||
"direction": "received",
|
||||
"type": "text",
|
||||
"text": "2 + 2",
|
||||
"time": "2026-09-29T03:14:34Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Every kind of message is posted, not only arithmetic, so `type` can be
|
||||
`image`, `file` or another kind, with the caption as `text`. Messages
|
||||
the bot sends, its replies and those sent through the API, are not
|
||||
posted.
|
||||
|
||||
A message is posted to each webhook at most once: there are no retries.
|
||||
A webhook misses it if the bot cannot reach it, if it does not answer
|
||||
within 10 seconds, or if it answers with a status other than 2xx; a
|
||||
redirect is not followed, and counts as such a status. So that no
|
||||
webhook holds up the bot's replies, messages wait in a queue, and the
|
||||
bot posts four at a time; a message that arrives while 100 wait is not
|
||||
posted. Posting goes on while the bot stops; once its chat client has
|
||||
exited, posts in progress are abandoned, and messages still waiting are
|
||||
not posted.
|
||||
|
||||
The bot logs two warnings, which never hold a webhook's URL, as it can
|
||||
carry a secret, nor a message's text:
|
||||
|
||||
- `dropping a message for the webhooks: the queue is full`, with
|
||||
`chat_id` and `message_id`, for a message that found the queue full;
|
||||
- `posting a message to a webhook`, with `webhook_id`, `message_id` and
|
||||
either the `status` the webhook answered with or the `error` that
|
||||
ended the post, for each post that got no 2xx answer, abandoned posts
|
||||
included. The error can name the webhook's host.
|
||||
|
||||
## Entrypoints
|
||||
|
||||
This repo adheres to the
|
||||
@@ -405,7 +451,13 @@ container.
|
||||
each rewrites the file whole: the bot writes a temporary file beside
|
||||
it, named `webhooks.json.` and digits, and renames it over the old
|
||||
one. A crash leaves the old file or the new one, never part of one,
|
||||
and at worst a stray temporary file, which the bot ignores.
|
||||
and at worst a stray temporary file, which the bot ignores. Messages
|
||||
are posted to the webhooks by four goroutines serving a queue. The
|
||||
goroutine that delivers the chat client's events, and also its
|
||||
answers, only puts each message in the queue, never waiting: not for a
|
||||
webhook, and not for the list of webhooks, which waits while the file
|
||||
is written. Posting goes on while the bot stops, until the chat client
|
||||
has exited.
|
||||
- **Replies**: for each text message a contact sends in a direct chat,
|
||||
the bot sends back the result, as a reply quoting the message. Group
|
||||
messages, files and the bot's own messages are ignored.
|
||||
|
||||
Reference in New Issue
Block a user