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

# Ingress: OAuth & Webhooks

> Every way an external system reaches CONA — which routes sit behind the auth proxy, which verify themselves, and how

# Ingress: OAuth & Webhooks

42 API route handlers in the webapp. They do **not** share one trust model. This page
records exactly which ones the auth proxy protects and what the others do instead.

## The trust boundary

```mermaid theme={null}
flowchart TB
    External["External systems<br/>providers · Stripe · Shopify"]
    Users["Organisation users"]

    Matcher{"proxy.ts matcher"}

    subgraph Protected["Behind the Auth0 proxy"]
        OAuthCB["11 OAuth callbacks<br/>amazon · shopify · paypal · otto<br/>tiktok · orderchamp · mollie<br/>bank-account · google"]
        PartnerAPI["8 partner API routes<br/>customers · sales-invoice<br/>sales-credit-note · sales-order<br/>payment · catalog-products<br/>integrations · deferred-revenue-schedules"]
        Feature["5 feature routes<br/>ar-recon · sammelbeleg<br/>accounting/periods<br/>document formats · google-sheets export"]
        Platform["copilotkit · axiom/ingest<br/>debug/session · user/* · portal/pdf"]
    end

    subgraph SelfVerify["Exempt — self-verifying"]
        StripeHook["api/webhooks/stripe<br/>signature vs STRIPE_WEBHOOK_SECRET"]
        ShopifyHook["api/webhooks/shopify/gdpr/*<br/>HMAC via verifyAndParseWebhook"]
        StripeApp["api/stripe-app/*<br/>authenticateStripeAppRequest + rate limit"]
        Health["api/health<br/>no auth — readiness probe"]
    end

    Users --> Matcher
    External --> Matcher
    Matcher -->|"matched"| Protected
    Matcher -->|"excluded by pattern"| SelfVerify

    classDef boundary fill:#f8fafc,stroke:#94a3b8,color:#334155

    classDef app fill:#dbe2fb,stroke:#3B56C5,color:#111827
    classDef external fill:#fff4dd,stroke:#c98a12,color:#111827
    classDef actor fill:#eceff4,stroke:#64748b,color:#111827

    class OAuthCB,PartnerAPI,Feature,Platform app
    class StripeHook,ShopifyHook,StripeApp,Health external
    class Users,External actor
    class Protected,SelfVerify boundary
```

The matcher (`apps/webapp/proxy.ts:288`):

```
"/((?!_next/static|_next/image|api/webhooks|api/health|api/stripe-app).*)"
```

## Exempt routes — verified, one by one

Every exempt route was checked. None are unintentionally open.

| Route                                              | Own authentication                                                                                    | Evidence         |
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ---------------- |
| `api/webhooks/stripe`                              | Stripe signature verified against `STRIPE_WEBHOOK_SECRET`; missing header → 400, missing secret → 500 | `route.ts:16-37` |
| `api/webhooks/shopify/gdpr/shop/redact`            | Shopify HMAC via `verifyAndParseWebhook`                                                              | `route.ts:2,22`  |
| `api/webhooks/shopify/gdpr/customers/redact`       | same                                                                                                  | —                |
| `api/webhooks/shopify/gdpr/customers/data_request` | same                                                                                                  | —                |
| `api/stripe-app/integration`                       | `authenticateStripeAppRequest` + rate limit                                                           | `route.ts:50-54` |
| `api/stripe-app/callback`                          | same                                                                                                  | —                |
| `api/stripe-app/billing/[stripe_id]`               | same                                                                                                  | —                |
| `api/stripe-app/payment/[stripe_id]`               | same                                                                                                  | —                |
| `api/health`                                       | **none, by design** — Playwright readiness probe                                                      | `route.ts:5`     |

<Check>
  All eight non-health exempt routes verify a provider signature before doing any work. The
  exemption is a deliberate design — webhook senders have no Auth0 session — not an oversight.
</Check>

## OAuth callback flow

Provider connection is initiated from the settings UI and completes through a callback
that **is** behind the auth proxy — so the completing user is always an authenticated
organisation member.

```mermaid theme={null}
sequenceDiagram
    actor U as Org user
    participant Setup as Setup page
    participant P as Provider
    participant CB as Callback
    participant Inst as install.ts
    participant DB as DB

    U->>Setup: click connect
    Setup->>P: OAuth authorize redirect
    P-->>U: consent screen
    U->>P: approve
    P->>CB: redirect with code
    Note over CB: through proxy.ts — session required
    CB->>P: exchange code for tokens
    CB->>Inst: persist integration
    Inst->>DB: store credentials
    CB-->>U: back to settings
```

Participants: `Setup page` = `setup/integrations/[provider]/[id]`,
`Callback` = `api/[provider]/callback`,
`install.ts` = `lib/integrations/[provider]/install.ts`.

Core-side token handling lives in the `otto-oauth`, `stripe-app-oauth`, and `finapi`
domains of `@cona/core`.

## Route inventory

| Purpose         | Count | Routes                                                                                                                                                                                                            |
| --------------- | ----: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| OAuth callbacks |    11 | `amazon/callback`, `bank-account/callback`, `google/auth`, `google/callback`, `mollie/callback`, `orderchamp/callback`, `otto/callback`, `paypal/callback`, `shopify/auth`, `shopify/callback`, `tiktok/callback` |
| Partner API     |     8 | `customers`, `sales-invoice`, `sales-credit-note`, `sales-order`, `payment`, `catalog-products`, `integrations`, `deferred-revenue-schedules`                                                                     |
| Stripe App      |     4 | `integration`, `callback`, `billing/[stripe_id]`, `payment/[stripe_id]`                                                                                                                                           |
| Webhooks        |     4 | `webhooks/stripe`, three Shopify GDPR routes                                                                                                                                                                      |
| Feature         |     5 | `ar-recon`, `sammelbeleg`, `accounting/periods`, `documents/[documentId]/formats/[formatId]`, `export/google-sheets`                                                                                              |
| Platform        |     5 | `health`, `copilotkit`, `axiom/ingest`, `debug/session`, `integrations/xentral/attachments/[attachmentId]`                                                                                                        |
| User            |     3 | `user/find`, `user/create`, `users/check-email-verified`                                                                                                                                                          |
| Portal          |     1 | `portal/pdf/[shareToken]`                                                                                                                                                                                         |
| Dev             |     1 | `dev/oauth-mock`                                                                                                                                                                                                  |

`stripe-app/callback` is an OAuth callback but is counted under Stripe App because it is
exempt from the proxy while the other 11 callbacks are not.

## Notes

**Data does not enter through these routes.** Marketplace and payment data is pulled by
Temporal activities, not pushed to the webapp. Webhooks here are GDPR requests and Stripe
billing events only. See [Integrations](/architecture/integrations-adapters).

**`/dev` and `/api/dev` return 404 outside dev and staging** — enforced in `proxy.ts:237-248`,
after the session check.

**Privileged access is audit-logged.** Requests to `/admin` and `/dev` emit a security
event (`proxy.ts:251-259`) to the Axiom security dataset.

**The portal has a separate ingress model entirely** — no middleware, share token plus
postal-code verification. See [Portal](/architecture/portal-architecture).
