← Все статьи
Новости14 мин чтения

fal.ai API перед запуском генерации: очередь и webhook

Асинхронный контракт fal.ai API: очередь, webhook, повторная доставка. Как связать callback с job и сделать обработку идемпотентной до запуска выдачи.

Обложка статьи: fal.ai API перед запуском генерации: очередь и webhook

Второй webhook часто опаснее первого пропавшего. Если fal.ai успешно завершил job и повторно прислал тот же callback, а обработчик наивно доверяет каждому входящему POST, приложение выдаёт пользователю вторую копию результата или списывает расход дважды в собственном учёте, хотя генерация была одна. Первую поломку, отсутствующий webhook, разработчик обычно замечает быстро: пользователь пишет, что результата нет. Вторую почти никто не замечает сразу, потому что внешне всё работает: результат есть, подпись валидна, ошибок нет.

Повтор доставки здесь не исключение, а часть контракта. fal.ai прямо пишет в документации, что первичная доставка webhook ограничена таймаутом 15 секунд, а при неудаче повторяется до 10 раз в течение двух часов (документация fal.ai, доступ 18 июля 2026), и отдельно требует от интеграторов делать обработчик идемпотентным, готовым выдержать повторную доставку для одного и того же request_id.

Если ты пишешь медиа-функцию поверх fal.ai API и планируешь запускать пользовательскую выдачу по приходу callback, вопрос ниже практический: как принять callback только после проверки его связи с исходным job и как сделать повторную доставку безопасной для продукта. Часть разработчиков, ищущих интеграцию, вбивает в поиске fal ai api и с ходу попадает в раздел queue, пропуская то, что между отправкой и результатом лежит отдельный транспортный контракт со своими гарантиями и своими повторами; именно этот промежуток решает, будет ли выдача надёжной.

Когда часть генераций идёт через несколько провайдеров сразу (одни модели напрямую у fal.ai, другие через совместимый маршрут вроде provod.ai), у каждого маршрута свой контракт доставки, и проверять его нужно отдельно. Начнём с того, что вообще возвращает очередь.

Платите в рублях за AI-модели без наценки на токены через provod.ai

Что возвращает очередь fal.ai и где рождается идентификатор

Асинхронный сценарий начинается не с webhook, а с очереди. Запрос уходит в queue API: через fal_client.submit(), fal.queue.submit() или обычный REST POST на https://queue.fal.run/{endpoint}. В ответ приходит request_id вместе с набором tracking-URL (документация fal.ai, доступ 18 июля 2026). Дальше job проходит три состояния: IN_QUEUE, затем IN_PROGRESS, затем COMPLETED. Статус можно опросить по {status_url}, готовый результат забрать по {response_url}, а пока задача стоит в очереди, отменить её PUT-запросом на {cancel_url}.

request_id: первичный ключ всей истории. Он появляется в момент submit, до того как модель вообще начала работу, и именно он потом придёт в теле webhook. Если это значение нигде не сохранено на своей стороне, связать будущий callback с конкретным пользователем и конкретной операцией будет нечем. Поэтому первый практический шаг находится не в обработчике callback, а в момент отправки: запиши request_id, идентификатор пользователя и текущее состояние job в свою таблицу сразу после submit, ещё до того как результат существует.

Timeline очереди fal.ai: три состояния job и опорные URL, request_id появляется при submit

Как выглядит webhook и почему второй раз опасен

Если при submit передан webhook_url, fal перестаёт ждать поллинга и сам делает POST с результатом на указанный endpoint (документация fal.ai, доступ 18 июля 2026). В теле приходят три ключевых поля: request_id, status со значением OK или ERROR и payload с выходом модели. При ошибке дополнительно заполняются error и payload_error. Само по себе тело выглядит как обычный колбэк: результат готов, можно действовать.

Ловушка рядом, в поле gateway_request_id. Оно совпадает с request_id в обычном случае и отличается от него только тогда, когда неуспешный запрос был повторён на уровне gateway. Именно по этому полю интеграция обязана сводить доставку обратно к исходному job, когда были ретраи. Наивная логика «пришёл request_id, значит это новая операция» ломается ровно здесь: транспорт повторил доставку, а приложение прочитало её как второе событие.

Телу нельзя доверять вслепую. Каждый POST несёт заголовки X-Fal-Webhook-Request-Id, X-Fal-Webhook-User-Id, X-Fal-Webhook-Timestamp (Unix-секунды) и X-Fal-Webhook-Signature (hex). Все четыре независимы от тела запроса, и по ним можно вести журнал ещё до разбора payload. Подпись проверяется публичными ключами ED25519 с JWKS-эндпоинта https://rest.fal.ai/.well-known/jwks.json; документация советует кэшировать ключи, но обновлять их минимум раз в 24 часа, а таймстамп проверять с допуском около 5 минут перед тем, как принять доставку (документация fal.ai, доступ 18 июля 2026). Проверка подписи и свежести защищает от подделки, но не защищает от честного повтора: повторный callback подписан ничуть не хуже первого.

Минимальный обработчик, который сначала проверяет транспорт, а потом решает про идемпотентность:

import time, requests from nacl.signing import VerifyKey

JWKS\_URL = "https://rest.fal.ai/.well-known/jwks.json"

def verify(headers, raw\_body): ts = int(headers["X-Fal-Webhook-Timestamp"]) if abs(time.time() - ts) > 300:            # допуск \~5 минут raise ValueError("stale timestamp") sig = bytes.fromhex(headers["X-Fal-Webhook-Signature"]) for key in load\_jwks\_cached(JWKS\_URL):     # кэш с refresh <= 24h try: VerifyKey(key).verify(raw\_body, sig) return True except Exception: continue raise ValueError("bad signature")

def handle(headers, body, raw\_body): verify(headers, raw\_body) job = body["gateway\_request\_id"]           # ключ сведения при ретраях if seen(job):                              # уже обработан -> идемпотентно return 200, "duplicate ignored" mark\_processing(job, headers["X-Fal-Webhook-Request-Id"]) if body["status"] == "OK": deliver\_once(job, body["payload"])     # ровно одна выдача на job else: record\_error(job, body.get("error")) commit(job) return 200, "ok"

Ключевое решение здесь умещается в двух строках: сведение делается по gateway_request_id, а seen(job) обращается к сохранённому состоянию, а не к памяти процесса. Без внешнего хранилища состояния идемпотентности не существует.

Таблица полей тела и заголовков webhook fal.ai с выделенным gateway_request_id и политикой повтора

Reconciliation log: журнал, который отличает job от доставки

Идемпотентность возникает не из флага в коде, а из того, что ведётся журнал соответствия. Проверочный сценарий простой: выполнить один реальный job и записать всю цепочку целиком, от исходного запроса через request_id и gateway_request_id, первый callback, повторный callback и итоговое идемпотентное действие. Такой webhook-reconciliation log «job—callback—проверка связи—повтор—идемпотентный итог» и есть артефакт, который подтверждает или опровергает тезис: если повторный callback для того же job создаёт второй продуктовый результат или второй расход, обработка не идемпотентна.

Журнал строится на связке идентификаторов, а не на теле сообщения. Строка появляется при submit, когда есть request_id, но ещё нет результата. Она обновляется на первом callback, когда сверены gateway_request_id, подпись и таймстамп и результат выдан ровно один раз. И она не меняет пользовательское состояние на любом повторе: повтор фиксируется как факт доставки ради наблюдаемости, но в новую операцию не превращается. На практике это означает, что состояние job хранится отдельно от факта доставки: одна запись job может быть связана с несколькими доставками, и только первая успешная доводит job до выдачи.

Здесь стоит честно развести уровни уверенности. Контракт очереди, webhook, тарификации и хранения — внешние факты из документации fal.ai. Правило «повторный callback не должен менять итог, если он связан с уже обработанным job» является производным выводом, который проверяется журналом, а не готовой цитатой из документации. Сам журнал на момент написания остаётся предложенным методом, а не завершённым результатом: он покажет повторяемую связь запроса, job и callback в одном практическом сценарии и не докажет универсальную надёжность очереди для всех моделей fal.ai сразу.

Диагностический маршрут обработки callback fal.ai с идемпотентной веткой при повторе

Стоит ли хранить состояние: три стратегии и их цена

Спорный дефолт, с которого многие начинают, звучит соблазнительно: успешный callback можно сразу превращать в новую выдачу. Он работает ровно до первого повтора. Компромисс здесь прямой: идемпотентная обработка требует хранилища состояния, зато убирает двойную выдачу результата и повторный расход в продукте. Три стратегии обработки и то, чем каждая расплачивается:

Стратегия обработки callbackЧто хранишьЧто происходит при повторной доставкеКогда допустимо
Каждый callback запускает новую операциюничеговторая выдача и/или вторая запись расходапочти никогда для пользовательской выдачи
Дедуп по request_id в памяти процессаключ до перезапускапосле рестарта или на втором инстансе повтор проходит как новыйодиночный процесс, некритичная выдача
Reconciliation log по gateway_request_id в хранилищесвязка job, доставок и итогаповтор фиксируется, состояние не меняетсяпродуктовая выдача и учёт расходов

Строки таблицы работают и как мини-фикстура для регресса: один и тот же повторный callback прогоняется против каждой стратегии, и видно, где именно появляется вторая операция. Первые две строки в таблице не рекомендации, а отрицательные примеры: они показывают, какой именно повтор каждая из них пропускает.

Двойной расход: где он реально возникает, а где нет

С деньгами важно не создать панику там, где её нет. Тарификация fal.ai привязана только к успешному inference-выводу: запрос, упавший с HTTP 500 и выше, не тарифицируется, а время ожидания в очереди до того, как раннер взял job, тоже бесплатно (документация fal.ai, доступ 18 июля 2026). Отсюда следует вывод, важный, но именно выводной: повторная доставка webhook для уже завершённого job сама по себе не запускает новый inference-прогон, поэтому второго списания на стороне fal.ai не создаёт. Это разумное следствие соседних правил биллинга, а не отдельная фраза, которую fal.ai написала прямо про повторную доставку webhook, и его стоит проверить на собственном аккаунте.

Опаснее собственный учёт приложения. Если расход считается по факту получения callback, а не по факту завершения job, повторная доставка превращается в двойную строку в биллинговой таблице продукта, даже когда у fal.ai списание одно. Поэтому reconciliation log ведут по идентификатору job, а не по событию доставки: расход привязывается к завершению job, а доставка остаётся только источником наблюдаемости.

Дальше встаёт практический вопрос про сам маршрут доставки, если часть нагрузки уже вынесена за пределы одного провайдера. provod.ai даёт единый API, совместимый с SDK OpenAI и Anthropic: для подключения меняются base_url и ключ, а дальше можно работать через привычный клиент, IDE, агента или бота, выбирая модели вроде Claude, GPT, Gemini, DeepSeek или Qwen из текущего каталога платформы. Оплата идёт с одного рублёвого баланса картой РФ, через СБП или по счёту, без VPN и без зарубежных карт, а доступ к покрытым моделям идёт по официальным ценам провайдеров без наценки самого provod.ai. Для медиа-функции это отдельный совместимый маршрут со своим контрактом доставки: подключать его можно параллельно с fal.ai, но он не заменяет webhook fal.ai и не наследует его гарантии повтора. Устойчивость к перебоям здесь про другое: мультиканальная маршрутизация снижает зависимость от одного временно недоступного апстрим-канала, а идемпотентность собственной выдачи всё равно остаётся задачей приложения.

Матрица фактов биллинга и хранения fal.ai с пометкой, где заканчивается факт и начинается вывод

А если callback опоздал: живёт ли ещё результат

Повтор бывает не только двойным, но и поздним, в пределах двухчасового окна ретраев. Тогда встаёт отдельный вопрос: разрешится ли запоздалый callback в живой URL результата. JSON-пейлоады запроса и ответа, не сама сгенерированная медиа, хранятся на платформе fal 30 дней по умолчанию и питают историю запросов в дашборде; это хранение отключается на конкретный запрос заголовком X-Fal-Store-IO: 0 (документация fal.ai, доступ 18 июля 2026). Срок жизни самих медиафайлов на CDN регулируется отдельно, заголовком X-Fal-Object-Lifecycle-Preference (expiration_duration_seconds или null для бессрочного хранения), и документация прямо предупреждает: истёкшие файлы удаляются безвозвратно.

Практический вывод для обработчика: поздний или повторный callback может прийти уже после того, как медиа-URL истёк, если задан короткий lifecycle. Значит, идемпотентное действие не должно слепо полагаться на то, что ссылка из payload всё ещё отдаёт файл. Если результат нужен гарантированно доступным, его стоит забирать в собственное хранилище на первом успешном callback, а не дожидаться повтора. Конкретное число дней дефолтного хранения CDN-медиа здесь намеренно не называется: на первичной странице media-expiration оно на момент проверки не подтвердилось как фиксированная величина, подтвердились только механизм управления через заголовок и сам факт безвозвратного удаления.

Чего это не решает

Один прогнанный job с журналом доказывает идемпотентность в одном сценарии, а не универсальную надёжность очереди fal.ai. Вывод по одной модели нельзя переносить на все медиа-job платформы: поведение конкретного callback и его защитных полей на другом эндпоинте до проверки остаётся неизвестным.

Идемпотентность обработчика не заменяет проверку подписи: это две разные защиты. Подпись отсекает подделку, идемпотентность защищает от честного повтора. Убрать одну и оставить другую значит оставить дыру.

Всё описанное выше — контракт fal.ai по состоянию на 18 июля 2026 года. Таймауты, число повторов и имена заголовков относятся к версионному поведению продукта и могут измениться без предупреждения, поэтому перед запуском выдачи стоит перепроверить значения на актуальной странице документации.

Отдельно: сторонний совместимый маршрут доставки не наследует контракт fal.ai. У него свой webhook или своя схема ответа, свои идентификаторы и свой журнал сведения, и идемпотентность для него придётся строить заново, под его собственный контракт.

FAQ

По какому полю сводить повтор: request_id или gateway_request_id?

По gateway_request_id. Оно совпадает с request_id в обычном случае и отличается только тогда, когда запрос был повторён на уровне gateway, то есть именно тогда, когда наивная дедупликация по request_id промахивается (документация fal.ai, доступ 18 июля 2026).

Спишет ли fal.ai деньги за повторную доставку webhook?

Повтор доставки не запускает новый inference, а тарифицируется только успешный вывод, поэтому второго списания на стороне fal.ai он не создаёт. Это вывод из правил биллинга, а не дословная формулировка про повтор, и его стоит проверить на своём аккаунте. Задвоиться скорее может собственный учёт продукта, если расход считается по событию доставки, а не по завершению job.

Что делать, если повторный callback пришёл, а медиа-URL уже недоступен?

Забирать файл в своё хранилище на первом успешном callback. Срок жизни CDN-медиа задаётся заголовком X-Fal-Object-Lifecycle-Preference, а истёкшие файлы удаляются безвозвратно (документация fal.ai, доступ 18 июля 2026).

Достаточно ли проверки подписи, чтобы не выдать результат дважды?

Нет. Подпись валидна и у повторной доставки. Нужна отдельная идемпотентная проверка связи callback с уже обработанным job.

Сколько времени приходят повторы?

До 10 попыток в окне 2 часа при неуспешной доставке, первичный таймаут — 15 секунд (документация fal.ai, доступ 18 июля 2026). Обработчик должен пережить весь этот интервал, не меняя состояние job.

provod.ai как отдельный совместимый маршрут доставки рядом с fal.ai

provod.ai — понятный расчётный контур для юридических лиц

Переведите AI из личных оплат в нормальную закупку: компания получает рублёвые расчёты, договор, счёт и закрывающие документы, а техническая команда — единый API.

В одном каталоге — актуальные модели для текста и медиа: GPT от OpenAI, Claude от Anthropic, Gemini от Google, Grok от xAI, DeepSeek, Qwen, GLM, Kimi и MiniMax; для изображений — Nano Banana 2 Pro и GPT Image; для видео — последние версии Seedance, Kling, Veo и Google Omni. Также доступны модели для reasoning, поиска, документов, эмбеддингов, музыки и аудио.

Документальный контур не маскирует дополнительную маржу: модели оплачиваются по официальным ценам 1:1, без наценки provod.ai.

Оформите AI для бизнеса: форма регистрации · цены на модели · защита данных по 152-ФЗ · реквизиты для договора

Источники

  • fal.ai, документация webhooks, доступ 18.07.2026: контракт callback, поля request_id, gateway_request_id и status, заголовки доставки, схема ED25519/JWKS, таймаут 15 секунд и до 10 повторов за 2 часа, требование идемпотентности.
  • fal.ai, документация queue, доступ 18.07.2026: request_id, три состояния job, эндпоинты status_url, response_url и cancel_url.
  • fal.ai, документация pricing, доступ 18.07.2026: тарификация только успешного вывода, бесплатное ожидание в очереди и запросы с HTTP 500 и выше.
  • fal.ai, документация media-expiration, доступ 18.07.2026: 30-дневное хранение JSON-пейлоадов, заголовок X-Fal-Store-IO, заголовок X-Fal-Object-Lifecycle-Preference и безвозвратное удаление истёкших файлов.
  • Проверенные факты продукта provod.ai; заявление владельца о лидерстве среди российских AI-агрегаторов датировано 15 июля 2026 года.