You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/reference.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -42,6 +42,7 @@ The quick lookup surface: one line and a minimal snippet per construct. For rule
42
42
|[the notify block / `attach: print`](/spec/glue#the-notify-block-and-attach-print)| send a message about a record - with the record's own document attached - from a process step, a transition or a schedule |
43
43
|[`schedules`](/spec/glue#schedules)| cron: notify or generate records per matching row |
44
44
|[`integrations`](/spec/glue#integrations-outbound-http)| outbound HTTP on a data change |
45
+
|[`integrations.payload`](/spec/glue#payload-the-declared-envelope)| the declared envelope a message carries, instead of the record as stored |
45
46
|[`inbound`](/spec/glue#inbound-webhooks)| a webhook that creates records |
46
47
|[`rollups`](/spec/glue#rollups-denormalised-parent-totals)| counts, sums, balance + status maintenance |
47
48
|[`settlements`](/spec/glue#settlements-payment-allocation)| auto-allocation of payments across open invoices |
Copy file name to clipboardExpand all lines: docs/spec/glue.md
+50Lines changed: 50 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -206,6 +206,56 @@ integrations:
206
206
207
207
The `@config:KEY` sugar resolves to a configuration lookup, so endpoints and secrets stay out of the source.
208
208
209
+
### payload — the declared envelope
210
+
211
+
Without a `payload`, the request body is the record as stored. That is only right when the receiver accepts the entity, and it has a cost even then: every column becomes part of a public contract, so adding a field silently changes what the outside world receives. A real integration contract is usually an *envelope* — a type, a version, an idempotency key, a timestamp, an identifier of the sender — which no arrangement of entity columns can produce.
212
+
213
+
`payload` declares that envelope, key by key:
214
+
215
+
```yaml
216
+
integrations:
217
+
- name: requestUserAssignment
218
+
event: { onCreate: UserInvitation }
219
+
method: POST
220
+
url: "@config:ASSIGNMENT_URL"
221
+
payload:
222
+
type: "user.assignment.requested" # literal
223
+
version: 1
224
+
messageId: "{uuid}" # minted per message
225
+
tenantId: "{tenant}" # execution context
226
+
appId: "@config:APP_ID" # configuration
227
+
email: email # a field of the record
228
+
role: role.name # one hop off a to-one relation
229
+
requestedAt: "{now}"
230
+
```
231
+
232
+
The value forms are the ones [`notify`](#notifications) already resolves, deliberately borrowed rather than invented: a **literal**, a **direct field**, or a **one-hop `relation.field`** of a to-one relation, which the generated sender reads from the related record it loads once. `@config:KEY` reads the configuration, as it does in `url`.
233
+
234
+
The **context tokens** are a closed set of four:
235
+
236
+
| token | value |
237
+
|---|---|
238
+
| `{uuid}` | a fresh identifier, minted per message — the idempotency key a receiver deduplicates on |
239
+
| `{now}` | the send time, as an ISO-8601 instant |
240
+
| `{tenant}` | the tenant the send runs for |
241
+
| `{user}` | the user behind the change that raised the event |
242
+
243
+
::: info Normative
244
+
A `payload` value MUST be one whole value in one of the declared forms. Interpolated text (`"Order {id} placed"`), a nested object and a list are NOT payload values and MUST be reported as authoring errors — a payload is a contract, not a template.
245
+
246
+
A path MUST resolve at most one hop; `a.b.c` MUST be rejected.
247
+
248
+
An unknown context token MUST be an authoring error, never an empty value in a sent message.
249
+
250
+
A `payload` MUST be rejected on a method that carries no request body.
251
+
252
+
Keys MUST be sent in the order they were declared.
253
+
254
+
A bare word that names no field and no to-one relation of the record is a **literal** — the only way to carry a one-word constant. A value braced as `"{name}"` is a reference and MUST resolve.
255
+
:::
256
+
257
+
Three value forms and four tokens is the cap, and the cap is the point: it expresses a frozen contract without the construct becoming a transformation language. A payload that needs more than this is an algorithm, and belongs in a hand-written handler — the honest hand-off.
258
+
209
259
## inbound — webhooks
210
260
211
261
Another system tells us — a webhook that ingests a JSON payload into an entity.
0 commit comments