Automations & Webhooks
Fullfinity ships a rule engine that runs actions when records change, on a time condition, or
when an external system calls in. It lives in the always-present core module, so every
database has it — there is no app to install. This page documents the parts a module author
builds on: which record events fire automations, how a model exposes methods to the
Call Method action, and the signature contract for inbound/outbound webhooks.
What fires an automation
Section titled “What fires an automation”An Automation targets one model and a trigger:
- On Creation / On Update / On Deletion — dispatched from the ORM write path. A rule is matched against each written record; if its condition (a stored filter) passes, its actions run. On Update additionally supports watched fields (fire only when one of a named set changes) and entering a filter (fire when a record that did not match the condition now does).
- On Time Condition — a record’s date/datetime field plus an offset (e.g. 3 days after
expiry_date). Evaluated by a background job. - On Webhook — an external
POSTto a per-rule URL (see below).
Event dispatch is designed so a model no automation watches costs nothing on write — the lookup that guards the seam is a couple of dictionary reads. You do not register anything to make a model automatable; any model is a valid target.
Actions and branching
Section titled “Actions and branching”An automation’s actions form a tree, not a flat list. Root actions run in sequence; a child action carries an optional branch condition and, with its whole subtree, runs only when the triggering record matches it. Two sibling children with complementary conditions are an if/else — e.g. on a new order: if total ≥ 10000 → notify the manager; otherwise → auto- approve. A child whose condition fails is skipped along with everything beneath it. A flat automation is simply one with only root actions.
Record-mutating actions (Update Record, Create Record, Call Method) run inside the triggering transaction, so they commit or roll back atomically with the change that fired them. Actions with external side effects — Send Email, Send Webhook — enqueue a row (an email message, a webhook delivery) in that same transaction and a background job performs the network I/O, so delivery is retried and never fires for a change that rolled back.
A per-transaction guard stops an automation whose action rewrites a record from re-triggering itself, and caps how deeply automation-driven writes may nest.
Exposing model methods to the “Call Method” action
Section titled “Exposing model methods to the “Call Method” action”The Call Method action invokes a method on the triggering record. There is no scripting and no decorator to add: the set of callable methods is derived by reflection from the model class. A method is offered when it is
- an instance method the model (or a module that extends it via
__inherit__) declares itself — the ORM’s own methods (create,update,filter, …) are never offered; - public (not underscore-prefixed) and not a
@Model.calculatecalculated-field method; - callable with no arguments beyond
self.
So any public, no-argument instance method you write on a model automatically becomes available
to automations the moment the module loads — and disappears if you remove it. The label shown
is the method name; its docstring’s first line is the description. The Builder app’s visual
editor lists these via GET /api/builder/automation/methods?model=<ModelName>, and the same
reflection validates a saved Call Method action, so the picker and the validator never disagree.
class HelpdeskTicket(Model): # ... async def escalate(self): """Bump priority and notify the team lead.""" # <- shown as the action's description ...escalate is now selectable as a Call Method action on HelpdeskTicket with no further
wiring. A method that needs arguments, is a classmethod (cls-first), or is private is not
offered — a Call Method action runs against a single record with no inputs.
Webhooks
Section titled “Webhooks”Inbound
Section titled “Inbound”An On Webhook automation owns a public endpoint:
POST /webhook/in/<database>/<token>The database is in the path because an external caller sends no session or db header; the
token identifies the rule and the request is authenticated by an HMAC signature, not a login.
Sign the raw request body and send it in X-Fullfinity-Signature:
signature = HMAC_SHA256(secret, "<unix_ts>." + raw_body)X-Fullfinity-Signature: t=<unix_ts>,v1=<hex(signature)>The signature is verified against the rule’s secret with a five-minute replay window; on
success the rule’s actions run with the JSON body as their payload. An invalid or stale
signature is rejected with 401.
Outbound
Section titled “Outbound”A Send Webhook action (or a standalone Webhook target) POSTs a JSON envelope to a
configured URL, signed the same way in X-Fullfinity-Signature (so a Fullfinity → Fullfinity
webhook round-trips). Delivery is queued and retried with backoff; each attempt and its
response are recorded, and a failed delivery can be re-queued. The envelope is:
{ "event": "update", "model": "SaleOrder", "record_id": 42, "data": { ... } }Verify an outbound delivery on the receiving side by recomputing the HMAC over
"<t>." + raw_body with the shared secret and comparing in constant time.