Async Resolution
Some workflows cannot finish during the original request. Payment confirmation, AI processing, email verification, and external API callbacks often complete later. FRL supports this with promise fields and resolved webhook handlers.
Create a Promise Field
Use promise(webhook, promiseId) in a process block.
post deposits {
process {
$post.payment = promise(
"/webhook/stripe",
$post.paymentReference
);
}
schema {
paymentReference: { type: "string", required: true, immutable: true }
payment: { type: "object", maxDepth: 2 }
paid: { type: "boolean" }
amountPaid: { type: "number", min: 0 }
}
}
The promise stores enough information for Frontbacked to match a later webhook to the original post.
Handle the Webhook
Use on resolved to update the post when a matching webhook arrives.
post deposits {
on resolved payment once
from "/webhook/stripe"
match $resolved.body.reference_id
{
$post.paid = true;
$post.amountPaid = $resolved.amount_total;
}
}
The handler above says:
| Part | Meaning |
|---|---|
payment | The promise field on the post. |
once | Treat resolution as idempotent where supported. |
from "/webhook/stripe" | Only handle webhook events from this path. |
match $resolved.body.reference_id | Extract the promise id from the webhook payload. |
| Handler body | Assign final values into $post. |
Resolved Context
During a resolved handler, $resolved contains the webhook request data. Common fields include body, headers, or payload values from the integration you are handling.
on resolved thumbnail
from "/webhook/ai"
match $resolved.header.["x-promise-id"]
{
$post.imageUrl = $resolved.image.url;
}
Bracket access is useful for header names or keys that contain characters such as hyphens.
Validate After Resolution
After a resolved handler updates $post, the schema runs again. This means resolved data must still match your declared schema.
schema {
imageUrl: { type: "string", max: 500 }
paid: { type: "boolean" }
amountPaid: { type: "number", min: 0 }
}
Keep schemas wide enough to allow pending and completed states, but narrow enough to reject malformed callback payloads.
Async Design Pattern
A common async post has:
- A client-submitted reference field marked
immutable. - A promise field created in
process. - A resolved handler that matches the callback.
- Final fields that are written only by the resolved handler.
- Permission rules that prevent frontend users from spoofing completion.
Example:
post verifications {
process {
$post.lookup = promise("/webhook/identity", $post.reference);
}
schema {
id: { type: "string", required: true, immutable: true }
userId: { type: "string", required: true, immutable: true }
reference: { type: "string", required: true, immutable: true }
lookup: { type: "object", maxDepth: 2 }
status: {
type: "string",
enum: ["pending", "approved", "rejected"],
required: true,
immutableUnless: $access.edit
}
checkedAt: { type: "datetime" }
}
create require[
$user.id == $post.userId
]
edit require[
$access.edit
]
on resolved lookup once
from "/webhook/identity"
match $resolved.body.reference
{
$post.status = $resolved.body.status;
$post.checkedAt = $resolved.body.checkedAt;
}
}
The frontend can create a pending verification, but only the trusted webhook or an editor can change the protected status field.