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

# Integrations

> Google, Bing, Clerk, Polar and the supporting third-party services.

## Google Analytics 4 and Search Console

A campaign can connect GA4 and Search Console under **different** Google accounts —
`ga4_google_account_id` and `gsc_google_account_id` are separate foreign keys to
`GoogleAccount`. This matters in agency setups where analytics and search access live with
different clients.

The OAuth flow is in `CampaignController` and `Services/GoogleOAuthService`:

| Route                                           | Purpose                                |
| ----------------------------------------------- | -------------------------------------- |
| `GET /connect-google`                           | Start the OAuth flow                   |
| `GET /google/callback`                          | OAuth callback                         |
| `POST /campaigns/{campaign}/update-ga4-account` | Assign a GA4 property                  |
| `POST /campaigns/{campaign}/update-gsc-account` | Assign a Search Console site           |
| `POST /campaigns/{campaign}/disconnect-google`  | Disconnect from one campaign           |
| `POST /disconnect-google`                       | Disconnect the account entirely        |
| `POST /skip-google`                             | Skip during onboarding                 |
| `GET /google-accounts`                          | List connected accounts                |
| `POST /update-google-nickname`                  | Rename a connected account for clarity |

Environment:

```ini theme={null}
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_ANALYTICS_CLIENT_ID=
GOOGLE_ANALYTICS_CLIENT_SECRET=
```

Domain verification is tracked per surface. `isDomainFullyVerified()` requires both
`ga4_domain_verified` and `gsc_domain_verified`; `hasDomainVerificationIssues()` is true if
either is missing. `POST /campaigns/{campaign}/recheck-domain` re-runs the check.

<Note>
  GA4 reports in the property's own timezone. `php artisan ga4:fetch-timezone` caches it per
  campaign — without it, daily traffic totals can be attributed to the wrong day.
</Note>

## Bing Webmaster Tools

Handled by `BingController` and `BingWebmasterService`, backed by the `BingAccount` model:

| Route                                        | Purpose                     |
| -------------------------------------------- | --------------------------- |
| `GET /connect-bing`                          | Start OAuth                 |
| `GET /bing/callback`                         | Callback                    |
| `POST /bing/sites`                           | List verified sites         |
| `POST /campaigns/{campaign}/update-bing`     | Assign a site to a campaign |
| `POST /campaigns/{campaign}/disconnect-bing` | Disconnect                  |

Credentials are configured through `config/services.php`.

## Clerk — authentication

Clerk is the identity provider. `Services/Clerk` holds `ClerkService` and
`ClerkApiException`; the frontend uses `@clerk/react`.

* `CLERK_PERMITTED_ORIGINS` is a comma-separated allow-list of authorized-party (`azp`)
  origins. Session tokens presented from an origin outside the list are rejected — this is a
  common cause of "works locally, fails on staging".
* `CLERK_WEBHOOK_SIGNING_SECRET` verifies inbound webhooks. Unsigned or mis-signed requests
  are rejected.
* `php artisan clerk:migrate-users` imports pre-existing local users into Clerk, preserving
  bcrypt passwords and linking records by `external_auth_id`.

Magic-link sign-in is supported alongside Clerk via the `LoginToken` model.

## Polar — billing

Polar acts as merchant of record. `Services/Polar/PlanCatalog` maps plans, `config/polar.php`
and `config/plans.php` hold the configuration, and the `Plan` and `Product` models mirror the
catalogue locally.

```ini theme={null}
POLAR_ACCESS_TOKEN=
POLAR_ORGANIZATION_ID=
POLAR_SERVER=sandbox
POLAR_WEBHOOK_SECRET=
POLAR_PRODUCT_STARTER=
POLAR_PRODUCT_GROWTH=
POLAR_PRODUCT_ENTERPRISE=
```

Register the webhook endpoint at `POST /polar/webhook` in the Polar dashboard, then copy that
endpoint's signing secret into `POLAR_WEBHOOK_SECRET`.

<Warning>
  Product IDs are environment-specific. Moving `POLAR_SERVER` from `sandbox` to `production`
  requires replacing all three `POLAR_PRODUCT_*` IDs with the production products.
</Warning>

Laravel Cashier and the `STRIPE_*` keys are still installed. Treat Polar as the active path
unless you are specifically working on the Stripe code.

## Supporting services

<AccordionGroup>
  <Accordion title="ScreenshotOne — SERP screenshots">
    `SCREENSHOTONE_ACCESS_KEY` / `SCREENSHOTONE_SECRET_KEY`. Used by
    `GoogleSearchScreenshotService` and `TakeSearchScreenshotJob` to capture the SERP behind a
    prompt run; surfaced as `PromptRun::$screenshot_url`.
  </Accordion>

  <Accordion title="PageSpeed Insights">
    `PageSpeedService` and `FetchPageSpeedInsightsJob` populate the `PagespeedInsight` model
    for the accessibility and site-intelligence views.
  </Accordion>

  <Accordion title="HubSpot — waitlist">
    `HUBSPOT_PORTAL_ID` / `HUBSPOT_FORM_ID`. `HubSpotWaitlistService` receives submissions
    from the pre-launch coming-soon page via `POST /waitlist`, throttled to 10 requests per
    minute.
  </Accordion>

  <Accordion title="Semrush — keyword fallback">
    `SEMRUSH_API_KEY`. Supplies keyword data to prompt suggestion when a campaign has no
    Search Console connection.
  </Accordion>

  <Accordion title="Mattermost — error logging">
    `MATTERMOST_ACCESS`, `MATTERMOST_CHANNEL_ID`, `MATTERMOST_LOG_ENABLED`. A custom log
    channel in `config/logging.php` posts errors to a Mattermost channel. Disabled by default
    and forced off during tests.
  </Accordion>
</AccordionGroup>
