Skip to content

Instantly share code, notes, and snippets.

@futzlarson
Created March 11, 2026 04:24
Show Gist options
  • Select an option

  • Save futzlarson/3cc71a282ede385c107e7d68c706fd90 to your computer and use it in GitHub Desktop.

Select an option

Save futzlarson/3cc71a282ede385c107e7d68c706fd90 to your computer and use it in GitHub Desktop.
GoTo oAuth setup

Step 2 — One-Time Setup: php artisan goto:setup {tenant_id}

All GoTo setup for a tenant is handled by a single artisan command (app/Console/Commands/Telephony/GoToSetup.php) run locally by a Curitics admin. The setup flow itself — including the OAuth callback — is handled entirely within the command via an embedded TCP socket listener. No Laravel routes or controllers are required during setup. (The webhook route and controller are separate and exist for the ongoing call event integration, not the setup.)

How environments are handled: The command is always run locally, pointed at the target environment's database. Once run against production, the tenant's GoTo config (tokens, channel ID, account key) lives in the production DB and flows down to UAT and local via the standard DB backfill — those environments never need to run goto:setup independently.

What the command does

php artisan goto:setup {tenant_id}
  1. Starts a temporary TCP socket listener on a random local port. This acts as the OAuth redirect_uri (http://127.0.0.1:{port}/callback) — GoTo redirects the browser back to it after the admin authorizes. No Laravel route is involved; the command handles the callback entirely within itself.

  2. Prints the authorization URL for the admin to open in a browser:

    https://authentication.logmeininc.com/oauth/authorize?response_type=code&client_id={client_id}&redirect_uri=http://127.0.0.1:{port}/callback&state={state}
    

    The admin visits this URL and logs in with the tenant's GoTo account credentials. GoTo then redirects the browser to the local socket listener. The command captures the authorization code from that HTTP request, sends a "you can close this tab" response, and immediately closes the listener — no polling loop, no pasting, no waiting.

  3. Exchanges the code for tokens and saves them to the Tenant record.

  4. Creates a webhook notification channel:

    POST https://api.goto.com/notification-channel/v1/channels/{tenant_name}-screen-pop
    Authorization: Bearer {access_token}
    Body: {
      "channelType": "Webhook",
      "webhookChannelData": {
        "webhook": {
          "url": "{webhook_base_url}/goto/webhook"
        }
      }
    }
    

    Webhook channel lifetime is permanent (~68 years). Stores the channelId on the Tenant.

    Local env — ngrok required: GoTo's Web Application Firewall (WAF) — a security layer on their API — rejects channel creation requests where the webhook URL body contains localhost or 127.0.0.1, returning a 403. Since GoTo's servers can't reach your local machine directly, you need a public tunnel. On a local environment the command requires a running ngrok tunnel and errors out if none is detected. Start ngrok before running:

    brew install ngrok   # install once
    ngrok http 8000      # gives you https://abc123.ngrok.io

    The ngrok URL is detected automatically via http://127.0.0.1:4040/api/tunnels. On non-local environments, APP_URL is used directly (it's a real public URL).

    Free tier caveat: The ngrok URL changes on every restart, requiring goto:setup to be re-run to update the webhook channel URL. A paid ngrok account with a reserved domain avoids this.

  5. Subscribes to call events:

    POST https://api.goto.com/call-events/v1/subscriptions
    Authorization: Bearer {access_token}
    Body: {
      "channelId": "{goto_channel_id}",
      "accountKeys": [
        {
          "id": "{goto_account_key}",
          "events": ["STARTING", "ENDING"]
        }
      ]
    }
    

    Verifies the response has accountKeys[0].status === 200 (the status is nested per-account-key, not at the top level).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment