> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fluz.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Configure OAuth App

> Set up your application's identity, scope ceiling, redirect URIs, and webhook endpoints — everything that has to be right before a user can grant you access.

Creating an application registers it. Configuring it is what makes it work.

This page walks the four tabs of the app editor in the order you should fill them out, and explains what each field actually controls. Get these right before you build your authorize flow — most OAuth integration failures trace back to a configuration mismatch rather than to code.

<Info>
  Reach the editor from **'Your apps'** in the developer dashboard, or directly at `https://fluz.app/for-developers/overview/{appId}`. Embedded widgets use the same tabs plus an additional **Installation** tab — see [Configure App Widget](/developers/configure-app-widget).
</Info>

<img src="https://storage.googleapis.com/fluz-fluz-file-uploads-prod-ricuyxowbwlfprel/documentation/Screenshot%202025-11-05%20at%202.02.46%E2%80%AFPM.png" alt="Selecting an app to configure from Your apps" width="300" />

***

## What's on each tab

| Tab              | What it controls                                            | Read it before you      |
| :--------------- | :---------------------------------------------------------- | :---------------------- |
| **Overview**     | Your app's public identity, and where your credentials live | Write any code          |
| **Permissions**  | The maximum scopes your app may ever request                | Build the authorize URL |
| **OAuth**        | Origin, redirect URIs, webhook endpoints                    | Handle the callback     |
| **Installation** | Generated embed code *(widgets only)*                       | Embed the widget        |

***

## Overview tab

Two things live here, and they serve very different audiences.

### Your credentials

<Warning>
  The **Client ID** and **Client Secret** are on this tab. Every other page in this section that tells you to authenticate a request with `Authorization: Basic base64(client_id:client_secret)` means these values. The **API Key** and **API Secret** also live here — those are a separate pair with a separate job. See [OAuth Applications](/build-a-platform/oauth-applications-overview) for which credential does what.

  Copy them into your secret manager. Never into a browser bundle, a mobile binary, or source control.
</Warning>

### Your public identity

The name, subtitle, description, avatar, and logomark are **what your users see on the consent screen** when they decide whether to hand your application access to their money. Treat these as product copy, not internal labels.

| Field           | Guidance                                                                                         |
| :-------------- | :----------------------------------------------------------------------------------------------- |
| **App name**    | The name your users already know you by. "Acme Payouts," not "acme-oauth-prod-v2."               |
| **Subtitle**    | One line on what the app does for them.                                                          |
| **Description** | A short, plain-language account of why you're asking for access.                                 |
| **Avatar**      | Required in practice. A consent screen with a blank avatar looks unfinished, and users hesitate. |
| **Logomark**    | Add it alongside the avatar.                                                                     |

<Note>
  Review this tab even if you think you filled it out during creation. The three text fields are collected in the creation wizard, where it's easy to type a placeholder and move on — and then ship it to a consent screen.
</Note>

***

## Permissions tab

This tab sets your **scope ceiling**: the maximum set of permissions your application may ever request from any user. It is not what any individual user has granted you.

<img src="https://storage.googleapis.com/fluz-fluz-file-uploads-prod-ricuyxowbwlfprel/documentation/Screenshot%202025-11-05%20at%202.03.06%E2%80%AFPM.png" alt="Scope selection on the Permissions tab" width="400" class="border" />

### How selected scopes reach the user

Users don't see raw enum values. Scopes you select are **grouped under a readable top-level header**, and it's the header that's presented for approval. A scope left unchecked is omitted entirely from what the user is asked to approve — and from what your app can ever request.

<img src="https://storage.googleapis.com/fluz-fluz-file-uploads-prod-ricuyxowbwlfprel/documentation/Screenshot%202025-11-05%20at%202.04.44%E2%80%AFPM.png" alt="How grouped scopes appear on the consent screen" width="400" class="border" />

### Required scopes

Some scopes are mandatory for the app or widget type you're configuring — without them the flow physically cannot run. These are gathered at the bottom of the tab, and **the user cannot unselect them** on the consent screen. You'll see them there; you don't choose them.

### Choosing your scopes

Start from what the flow in front of you needs, not from what you might need someday.

| If your integration...            | Request roughly                                                         |
| :-------------------------------- | :---------------------------------------------------------------------- |
| Pays users out from your platform | `MAKE_WITHDRAW`, `LIST_PAYMENT`                                         |
| Collects funds from users         | `MAKE_DEPOSIT`, `LIST_PAYMENT`                                          |
| Issues cards on a user's wallet   | `CREATE_VIRTUALCARD`, `EDIT_VIRTUALCARD`, `REVEAL_VIRTUALCARD`          |
| Distributes cards by link         | `CREATE_VIRTUALCARD`, `CREATE_SHARE_LINK`                               |
| Sells or redeems gift cards       | `LIST_OFFERS`, `PURCHASE_GIFTCARD`, `REVEAL_GIFTCARD`, `LIST_PURCHASES` |
| Manages a user's funding sources  | `LIST_PAYMENT`, `MANAGE_PAYMENT`                                        |

Full reference: [Application Scopes](/fluz-dashboard/application-scopes).

<Note>
  **Fewer scopes convert better.** The consent screen is the highest-drop-off step in your integration, and its length is set by this tab. A narrower ceiling also limits the blast radius if a token leaks. Ask for what today's flow needs; widen the ceiling when you build the next feature.
</Note>

### Scopes you can't self-select

`PCI_COMPLIANCE` is administered by Fluz at the application level, granted to developers who have demonstrated PCI DSS compliance, and cannot be requested when generating a token. If you need to handle raw card data yourself, talk to your Fluz account manager. If you don't want to, that's what [embedded widgets](/developers/widgets) are for — they keep card capture inside Fluz's PCI scope.

### Changing scopes later

The permission model is an **intersection** of the app-level ceiling and each user's grant, which has two practical consequences:

* **Adding** a scope here does *not* retroactively grant it on tokens users already issued you. Existing users must re-authorize before the new scope becomes effective for them.
* **Removing** a scope here narrows effective access immediately, for every user, regardless of what they previously approved.

Plan scope changes like schema migrations, not like config tweaks.

***

## OAuth tab

<img src="https://storage.googleapis.com/fluz-fluz-file-uploads-prod-ricuyxowbwlfprel/documentation/Screenshot%202025-11-05%20at%202.03.18%E2%80%AFPM.png" alt="Redirect URI configuration on the OAuth tab" width="400" />

### Origin

The domain that will host the flow — `example.com`, `app.example.com`. For embedded widgets this is the page the widget renders on, and it must match or the widget won't load.

### Redirect URIs

Where our authorization server is permitted to send the user after they approve or decline.

<AccordionGroup>
  <Accordion title="Rules">
    * Must be a **public** URL our servers can reach.
    * **No query parameters** on the registered URI. Use the `state` parameter to carry context instead.
    * Register **as many as you need** — one per environment, one per flow variant.
    * The URI you use at `/authorize` must be registered here, and the URI you send to `/token/exchange` must be **byte-identical** to the one you used at `/authorize`.
  </Accordion>

  <Accordion title="Byte-identical means byte-identical">
    These are four different URIs as far as the authorization server is concerned:

    ```text theme={null}
    https://app.example.com/oauth/finalize
    https://app.example.com/oauth/finalize/
    http://app.example.com/oauth/finalize
    https://App.Example.com/oauth/finalize
    ```

    Pick one canonical form, store it in a single constant, and use that same constant in both the authorize step and the exchange step. Hard-coding it twice is how mismatches happen.
  </Accordion>

  <Accordion title="Local development">
    Register your local callback explicitly — for example `http://localhost:3035/oauth/finalize`. It won't work unless it's on the list, and localhost URIs should not be left registered on a production app.
  </Accordion>
</AccordionGroup>

### Webhook URLs

Public REST endpoints that receive events from Fluz — how you learn that a transfer completed, a user dismissed a modal, or a verification resolved, without polling.

* Add **as many URLs as you like**.
* Subscribe each URL to **specific events**, so you can route different event families to different services.
* A URL with **no events selected becomes a catch-all** and receives everything. Convenient for development, noisy in production.

<Note>
  Your endpoint should acknowledge quickly and process asynchronously. Do the minimum work needed to accept the event, then hand it to a queue — a slow webhook handler turns into a delivery problem.
</Note>

***

## Verify before you build

Five minutes here saves an afternoon of debugging the authorize flow.

<Steps>
  <Step title="Credentials are out of the dashboard and into your secret store">
    Client ID, Client Secret, API Key, API Secret. Confirm nothing landed in a `.env` that's tracked in git.
  </Step>

  <Step title="The consent screen reads well">
    Name, subtitle, description, avatar, logomark all populated and written for your users.
  </Step>

  <Step title="Every scope your code calls is checked on Permissions">
    Walk your intended API calls and confirm each one's scope is enabled. A scope you request but haven't enabled here is silently dropped, not rejected — so this mistake surfaces later as a confusing permission error.
  </Step>

  <Step title="Your redirect URI is registered in its exact canonical form">
    Including protocol, host casing, and trailing slash.
  </Step>

  <Step title="Build one authorize URL by hand and open it">
    Assemble `/authorize` with `response_type=code`, your `client_id`, your registered `redirect_uri`, and your scopes, then load it in a browser. If the consent screen renders with your branding and the scopes you expect, your configuration is correct. If it errors, the message names what didn't match — and you've found it before writing any code.

    → [Client-facing OAuth grant flow](/client-facing-o-auth-grant-flow)
  </Step>
</Steps>

***

## Staging and production are separate apps

Environments do not share configuration. A production application is registered separately, with its own Client ID, Client Secret, API Key, and API Secret, and its own redirect URIs and webhook endpoints pointed at live hosts.

Nothing carries over from staging — including scope selections. Re-verify this entire page against your production app before launch. See [Deploying to Production](/deploying-to-production).

***

## Troubleshooting

| Symptom                                                      | Almost always                                                                                                                                            |
| :----------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/authorize` returns an error instead of a consent screen    | `redirect_uri` isn't registered, or `client_id` doesn't match this app                                                                                   |
| Consent screen shows fewer scopes than you requested         | The missing ones aren't checked on the Permissions tab                                                                                                   |
| Exchange at `/token/exchange` fails                          | `redirect_uri` doesn't byte-match the authorize step, or the Basic auth header encodes the parts separately instead of `base64(client_id:client_secret)` |
| API call fails on a permission you thought you had           | Scope is enabled at app level but wasn't in this user's grant — check the `scope` array returned by the exchange, not what you requested                 |
| `generateUserAccessToken` fails for a user who worked before | The app-level or user-level grant expired or was revoked. Both must be live                                                                              |
| Widget won't load on your page                               | **Origin** doesn't match the hosting domain                                                                                                              |
| No webhooks arriving                                         | URL isn't publicly reachable, or it's subscribed to events other than the ones firing                                                                    |

***

## Next steps

<CardGroup cols={2}>
  <Card title="Client-facing grant flow" icon="user-check" href="/client-facing-o-auth-grant-flow">
    Build the authorize URL and handle the callback.
  </Card>

  <Card title="Exchange an authorization code" icon="arrow-left-right" href="/exchange-an-o-auth-authorization-code">
    Turn a code into an access token and refresh token.
  </Card>

  <Card title="Application scopes" icon="key" href="/fluz-dashboard/application-scopes">
    The full scope reference.
  </Card>

  <Card title="Deploy to production" icon="rocket" href="/deploying-to-production">
    Re-register against live hosts and go live.
  </Card>
</CardGroup>
