Style guide

Public backend workflows and authentication modes

Public backend workflows should be public if and only if they need to be

A public backend workflow is an API endpoint.

If it can change data, send emails, create users, generate login links, update subscriptions, or trigger billing logic, assume someone can try to call it directly. They do not need to press the button in your app. They can send their own request with their own parameters.

So the first question is simple: does this backend workflow actually need to be public?

If it is only scheduled from inside Bubble, keep it private. If it is called by your own server-side API Connector setup, use the narrowest authentication mode you can. If it is called by Stripe, Zapier, Make, or another external service, it may need to be public, but the workflow still needs to verify the request before doing anything useful.

Authentication mode only gets the caller through the door

Bubble gives you three relevant authentication modes for public backend workflows:

  • None required

  • Admin only

  • User & admin

Use the narrowest one that works.

The common mistake is treating authentication as permission. A logged-in user is authenticated. The workflow still needs to check whether that user can delete another user, send platform emails, refund an order, or generate a magic login link.

Every public backend workflow should have two checks in your head:

  1. Who can call this endpoint?

  2. What does the workflow check before it changes anything?

The second check is where a lot of Bubble apps fall over.

Use admin only for internal server-side calls

Use Admin only when the endpoint is only meant to be called by your own server-side setup.

For example, say you have an API Connector call that hits a backend workflow called send account invoice email. That workflow accepts an Account and sends a billing email.

If normal logged-in users should never call that endpoint directly, User & admin is too broad. Use Admin only where possible.

Still keep the workflow narrow. A workflow that accepts an Account and sends one known invoice email is much safer than a workflow that accepts recipient, subject, and body and sends whatever the request says. Admin authentication should not turn untrusted parameters into a blank cheque.

Also keep sensitive API Connector parameters private where Bubble lets you. Do not put internal keys into front-end-visible parameters just because the call happens to work.

Use none required for real external webhooks

Use None required when the caller genuinely cannot authenticate as a Bubble user or admin token.

Stripe webhooks are the obvious example. Stripe needs to call your Bubble backend workflow when a payment, subscription, refund, or invoice event happens. That request is coming from outside your Bubble app.

With None required, put the security check inside the workflow.

For a Stripe webhook, that might mean:

  • check the Stripe event ID before trusting the event

  • reject events that are too old

  • ignore events you have already processed

  • check the event matches a Checkout Session, Customer, Subscription, or internal record you expected

  • terminate the workflow before updating Users, Orders, Subscriptions, Credits, or access fields if the check fails

The exact Stripe checks depend on how the app is built. Make the webhook prove itself before it changes your database.

Do not rely on the endpoint name being hard to guess. A random endpoint name is better than subscription_created, but obfuscation is not enough for billing logic.

Be careful with putting an admin API token into third-party webhook URLs. That can authenticate the request as admin, but the admin token may then sit inside the third-party service's settings. For most apps, I would rather verify the webhook inside the workflow.

Use user & admin only when users are meant to call the endpoint

Use User & admin when the backend workflow is intentionally part of a logged-in user flow.

Then check the user's permission inside the backend workflow.

For example, an admin impersonation flow might generate a magic login link for another user. The unsafe version is:

  1. public backend workflow accepts a User parameter

  2. workflow generates a login link for that User

  3. workflow only checks that the caller is authenticated

This is unsafe. If a normal logged-in user can call the endpoint with another user's unique ID, they may be able to generate a login link for that account.

The safer version checks that the caller is allowed to perform that exact action. One simple pattern is:

  1. Front-end workflow only starts when Current User is an admin.

  2. Front-end workflow creates a permission token for Current User.

  3. Backend workflow receives the target User and permission token.

  4. Backend workflow searches for a valid, unexpired, unused permission token for the requesting user.

  5. Backend workflow terminates if the token is missing or invalid.

  6. Backend workflow clears or marks the token as used.

You can implement that differently. The backend workflow should check the permission before it generates the link.

Use the same approach for workflows that delete users, change roles, send platform emails, issue credits, change subscription status, or touch billing records.

Do not expose endpoints just to control workflow order

Sometimes developers expose backend workflows because they want one step to finish before another step runs.

Check whether a custom event is enough first. Bubble custom events are often the cleaner way to sequence internal workflow logic. If Step 2 depends on data changed in Step 1, put Step 1 into a custom event where that makes the execution order clearer.

External services still need public endpoints. Scheduled backend work still belongs in backend workflows. Just do not make a workflow public when the actual problem is internal sequencing.

Review public backend workflows like this

For each public backend workflow, check:

  1. Can this be private?

  2. If it must be public, can it use Admin only?

  3. If it uses None required, how does it verify the webhook or external request?

  4. If it uses User & admin, what stops one user acting on another user's data?

  5. Does the workflow terminate before changing data when the check fails?

  6. Are API Connector parameters private where they should be?

  7. Would the workflow still be safe if someone called it directly with changed parameters?

Make backend workflows public deliberately, use the narrowest authentication mode, and put the actual permission check inside the workflow.

Was this helpful?