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.

Functions and webhooksprod
  • 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, Razorpay, GitHub or generic HMAC
Once per event
A retry or replay runs nothing

How server code runs

  1. 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.

  2. 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.

  3. 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.

appbricx.json
{ "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, X-Razorpay-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, Razorpay, 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.

Your coding agent builds it. Appbricx runs it.