Server functions and webhooks
Your own server code, and webhooks you can trust, with no server to run.
Some work can't happen in the browser: calling a weather or CRM API with a secret key, a check the user must not be trusted with, applying Stripe's events. A server function is a few lines of TypeScript that a screen runs with app.call. An inbound webhook checks the sender's signature on the raw body and runs each event once.
- POST/__appbricx/functions/get-weather200{ temp: 19, summary: "clear" }
- POST/hooks/<project>/stripe-events200Stripe-Signature verified, ran once
- POST/hooks/<project>/stripe-events200duplicate: true, nothing ran
- POST/hooks/<project>/github-push401SIGNATURE_INVALID
- app.call
- A screen runs your function and gets JSON
- Any HTTPS API
- ctx.http.fetch with keys from ctx.secrets
- Provider-signed
- Stripe, GitHub or generic HMAC
- Once per event
- A retry or replay runs nothing
How server code runs
Declare a workflow with "on": { "call": true, "roles": [...] } and one custom step. The code receives ctx, the input and the caller, and returns any JSON value.
Call it from a component with await app.call("get-weather", { city }). It runs as the caller, with row-level security on ctx.db, and reads its key from ctx.secrets, never from the browser.
For events pushed in, declare a webhook with verify: { provider: "stripe", secret: "STRIPE_WEBHOOK_SECRET" }. get_contract lists the URL to paste into Stripe, and each event id runs once.
{ "id": "get-weather",
"on": { "call": true, "roles": ["member"] },
"do": [{ "action": "custom", "code":
"const key = await ctx.secrets.get('WEATHER_API_KEY'); …
const res = await ctx.http.fetch(url, { headers }); …" }] }
{ "id": "stripe-events",
"on": { "webhook": "stripe-events",
"verify": { "provider": "stripe",
"secret": "STRIPE_WEBHOOK_SECRET",
"events": ["checkout.session.completed"] } } }
// in a component
const w = await app.call("get-weather", { city: "Lisbon" });
Server code without a server
Who may call
roles limits a function to some personas; without roles any signed-in user may call it, and public opens it to visitors. The server decides from the session.
Outbound HTTPS
ctx.http.fetch reaches any public HTTPS API, 30 seconds per request. Private and internal hosts are refused.
Secrets stay on the server
ctx.secrets.get reads the project's environment variables. A connected Stripe account supplies STRIPE_SECRET_KEY.
The rest of ctx
ctx.db as the caller, ctx.ai, ctx.integrations, ctx.notify, ctx.email, ctx.vars, ctx.log and ctx.app.url for links back into the app.
Provider-signed webhooks
Stripe-Signature, GitHub's X-Hub-Signature-256 or any HMAC header is checked on the raw body in constant time, and a stale Stripe timestamp is refused.
Your own signed webhooks
Other callers sign the body with the project's secret in x-appbricx-signature, and an idempotency key stops repeats within 24 hours.
Stripe subscriptions, wired end to end
The stripe-subscriptions template uses both halves: server functions start Stripe Checkout and open the billing portal, and a Stripe-signed webhook applies checkout, invoice and subscription events, so only the server marks a subscription active.
Server functions and webhooks: common questions
Can my app call a third-party API with a secret key?
Yes. Write a server function that reads the key with ctx.secrets.get and calls the API with ctx.http.fetch. A screen calls it with app.call, and the key never reaches the browser.
What limits apply to a server function?
One attempt with no retries, 30 seconds per run, a few concurrent calls per app, and 8000 characters of code. Each call counts as a workflow run and appears in the logs.
How are Stripe webhooks verified?
The Stripe-Signature header is checked against the raw body with your endpoint's signing secret. A wrong signature or a timestamp more than five minutes off is refused, and a repeated event id runs nothing.
Which webhook senders are supported?
Stripe, GitHub and any sender that signs with HMAC (generic-hmac, with your header, hex or base64 and an optional prefix). Other callers can sign with the project's own secret.