telega/idempotency
Webhook/update idempotency: drop updates Telegram re-delivers.
Telegram retries an update (same update_id) when it does not receive a
200 in time — on a slow response, a redeploy, or a network blip. Without
deduplication that means a command runs twice, which is a real problem for
non-idempotent actions (sending an invoice, charging Stars, granting a
reward).
deduplicate builds a pre-router middleware
that remembers each update_id in a KeyValueStorage
for a TTL window and stops any update whose id was already seen. Because
pre-router middleware runs sequentially inside the single bot actor, the
read-then-write below is race-free even under concurrent re-delivery.
import telega
import telega/idempotency
import telega/storage/ets
let assert Ok(store) = ets.new(name: "telega_dedup")
telega.new(token:, url:, webhook_path:, secret_token:)
|> telega.use_pre_handler(idempotency.deduplicate(
storage: store,
// Keep ids for an hour — comfortably longer than Telegram's retry window.
ttl_ms: 3600_000,
))
|> telega.with_router(router)
|> telega.init()
Use a persistent backend (Postgres/SQLite/Redis) when you run more than one
bot node or want dedup to survive a restart — the in-memory ets store is
per-node and cleared on VM restart.
Values
pub fn deduplicate(
storage storage: storage.KeyValueStorage(storage_error),
ttl_ms ttl_ms: Int,
) -> fn(bot.PreContext(dependencies)) -> bot.PreRouterResult
Build a pre-router middleware that drops updates whose update_id was
already processed within the last ttl_ms milliseconds.
On a storage error the update is let through (fail-open): processing an update twice is recoverable, silently dropping a legitimate one is not.