Consumer Gateways
Consumer gateways call an external service and do not return a result. Use script type consumerGateway.
Consumer gateway calls that fail with a transient error are automatically retried with a fixed backoff schedule: after 10 seconds, then 60 seconds, then 15 minutes. If all three attempts fail, the platform writes a system.logEntry with category consumer_gateway_retry and level ERROR, containing the gateway name and the last error message.
Errors that are not retried: HTTP 4xx responses (except 429 Too Many Requests). These indicate a configuration or data problem that retrying cannot resolve.
Mail
Script type: consumerGateway
Gateway name: Mail
Sends a transactional email. The body can be a literal value or rendered from a Handlebars template file.
| Parameter | Type | Required | Description |
|---|---|---|---|
to | Yes | Recipient email address | |
subject | TEXT | Yes | Email subject line |
body | TEXTBLOCK | Yes | Email body (HTML or plain text). Use templateFile to render from a template. |
ctaHref | TEXT | No | Call-to-action URL rendered as a button in the email |
ctaName | TEXT | No | Button label for the call-to-action link |
{
"name": "sendWelcomeMail",
"type": "consumerGateway",
"gateway": "Mail",
"scope": "OBJECT",
"trigger": "MANUAL",
"parameters": [
{ "name": "to", "formula": "emailAddress" },
{ "name": "subject", "value": "Welcome to the platform" },
{ "name": "body", "templateFile": "templates/welcomeMail" },
{ "name": "ctaHref", "formula": "sysInfo(\"appUrl\")" },
{ "name": "ctaName", "value": "Open application" }
]
}
UserInvitation
Script type: consumerGateway
Gateway name: UserInvitation
Grants a person access to the tenant by creating a user invitation for the specified email address.
| Parameter | Type | Required | Description |
|---|---|---|---|
email | Yes | Email address to invite | |
firstName | TEXT | No | First name of the invitee |
lastName | TEXT | No | Last name of the invitee |
personRef | TEXT | No | Reference to an existing person object (e.g. /persons/{uuid}) |
role | TEXT | No | system.profileRole key to assign to the invited user (admin/member/viewer). Defaults to member when omitted. |
Behaviour
The gateway looks up whether a user profile already exists for the given email address:
- No existing profile — a new user profile is created and an invitation email is sent.
- Existing profile, no active access — the person object is linked to the existing profile,
firstNameandlastNameare updated if provided, and an invitation email is sent. - Existing profile with active access — the person object is linked to the existing profile and
firstName/lastNameare updated if provided. No invitation record is created and no email is sent.
role is only applied when creating a new profile, or filling in a missing role on an existing one — it never overwrites a role already assigned to an existing profile, even on re-invite.
The gateway runs as a background process. There is no return value or inline feedback available to the calling script.
Webhook
Script type: consumerGateway
Gateway name: Webhook
Sends an HTTP request to a configurable endpoint when a trigger fires. Use this to notify external systems of data changes without writing a custom integration.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
url | TEXT | Yes | Target URL. Must be HTTPS. |
method | TEXT | No | HTTP method (GET, POST, PUT, etc.). Default: POST. |
payload_<name> | TEXT | No | A payload field. For POST/PUT: assembled into a flat JSON object body. For GET: appended as a query parameter. |
header.<name> | TEXT | No | Additional request header (e.g. header.Authorization). |
All payload values are serialized as strings. Only TEXT values are supported for payload and header parameters.
{
"name": "notifyWebhook",
"type": "consumerGateway",
"gateway": "Webhook",
"scope": "OBJECT",
"trigger": "UPDATE",
"parameters": [
{ "name": "url", "formula": "systemSetting('rulebooks_webhook_url')" },
{ "name": "method", "value": "POST" },
{ "name": "payload_eventType", "value": "COMPANY_UPDATED" },
{ "name": "payload_href", "formula": "_href" }
]
}
This fires on every UPDATE and POSTs {"eventType":"COMPANY_UPDATED","href":"..."} to the configured URL.
DatastoreReplicator
Script type: consumerGateway
Gateway name: DatastoreReplicator
Replicates a configured subset of an object's fields to an external analytical datastore so customers can build dashboards (Looker Studio, Power BI) and run analyses without burdening the operational database.
The gateway does not write to the external store on every trigger. Events are buffered and flushed as micro-batches at a configurable interval. The default flush interval is 15 minutes; override per destination with _flushInterval.
Supported platforms: Google BigQuery (via load jobs).
Tenant setup
The script definition is part of the model and can be built without any GCP configuration in place. The setup below is performed by the tenant administrator when activating the integration on a live tenant.
-
GCP project — use your own GCP project (BigQuery is billed against your quota, not the platform's).
-
Enable the BigQuery API in the GCP project.
-
Create a service account with the following roles on the target dataset:
BigQuery Job User(project level)BigQuery Data Editor(dataset or table level)
-
Download the service account key as JSON.
-
Store the key as a secret system setting — do NOT use a plain
system.systemSetting, because itsvaluefield is readable back via the API. Usesystem.secretValueinstead:POST /system/{tenantId}/secretValues
{
"name": "bigquery-credentials",
"secretValue": "<paste the full service account JSON here>"
}The
secretValuetype stores the value encrypted and excludes it from all API responses. During formula resolution (including gateway parameter evaluation)systemSetting('bigquery-credentials')returns the unmasked value. -
Optionally store
_projectIdand_datasetas plainsystem.systemSettingobjects if they are shared across multiple classes.
If the connection parameters are not (yet) configured on the tenant, the gateway silently skips buffering — no error is thrown and no data is written.
Parameters
Parameters beginning with _ are connection/routing configuration. Every other parameter becomes a column in the target table; its name is the column name and its value is a formula-resolved field or expression.
| Name | Type | Required | Description |
|---|---|---|---|
_type | TEXT | Yes | Target platform. Currently only BIGQUERY. |
_projectId | TEXT | Yes | GCP project ID that owns the BigQuery dataset. |
_dataset | TEXT | Yes | BigQuery dataset name. |
_table | TEXT | Yes | BigQuery table name. Created automatically on first flush if it does not exist. |
_credentials | TEXT | Yes | Service account key JSON. Always resolve from a secretValue via systemSetting(). |
_flushInterval | TEXT | No | Override the flush interval (e.g. 15m, 3h). Default: 15 minutes. Uses Spring duration notation. |
<columnName> | any | No | Any non-_ parameter becomes a column. The column name is the parameter name; the value is the resolved formula. |
The standard column id (the object's UUID) is always written automatically.
Type mapping
| Platform value type | BigQuery type |
|---|---|
INTEGER | INT64 |
DECIMAL, CURRENCY, PERCENTAGE | NUMERIC |
BOOLEAN | BOOL |
DATE | DATE |
DATETIME | TIMESTAMP |
TIME | TIME |
| All others | STRING |
The table schema is derived from the column values on first flush and extended automatically when new columns appear (ALTER TABLE … ADD COLUMN). Removing a column from the script does not drop it from the table.
Triggers
Supports CREATE, UPDATE, and DELETE. On DELETE the gateway issues a hard delete by id on the target table. Because the object no longer exists at execution time, only the id is available — no field formulas are evaluated.
{
"name": "syncToAnalytics",
"type": "consumerGateway",
"gateway": "DatastoreReplicator",
"scope": "OBJECT",
"trigger": ["CREATE", "UPDATE", "DELETE"],
"parameters": [
{ "name": "_type", "formula": "systemSetting('bq.type')" },
{ "name": "_projectId", "formula": "systemSetting('bq.projectId')" },
{ "name": "_dataset", "formula": "systemSetting('bq.dataset')" },
{ "name": "_table", "value": "invoices" },
{ "name": "_credentials", "formula": "systemSetting('bq.credentials')" },
{ "name": "status", "formula": "status" },
{ "name": "amount", "formula": "amount" },
{ "name": "invoiceDate", "formula": "invoiceDate" },
{ "name": "clientName", "formula": "client.legalName" }
]
}
Relations are not a special case — map any relation field using a formula (client.legalName, country.isoCode, etc.) as a regular column. If you need a tenantId column, add it explicitly: { "name": "tenantId", "formula": "sysInfo(\"tenantId\")" }.
Notes
_projectId,_dataset, and_tablemay be plainsystem.systemSettingobjects._credentialsmust always be asystem.secretValue.- Each flush de-duplicates to the latest record per
idbefore writing, so rapid CREATE/UPDATE sequences result in a single row at the destination. - If a flush fails, events are retained and retried automatically on the next flush cycle.