Skip to content

Feature: Embedded Signup#127

Open
Saifallak wants to merge 33 commits intoshridarpatil:mainfrom
Saifallak:embded-signup
Open

Feature: Embedded Signup#127
Saifallak wants to merge 33 commits intoshridarpatil:mainfrom
Saifallak:embded-signup

Conversation

@Saifallak
Copy link
Contributor

@Saifallak Saifallak commented Feb 3, 2026

Original PR: Saifallak#6

Feature: WhatsApp Embedded Signup Integration

This PR implements the full Embedded Signup flow for WhatsApp Business Platform, allowing users to connect their accounts directly through the application using their Facebook credentials.

Frontend Changes

  • AccountsView.vue:
    • Added "Connect with Facebook" button triggered by launchWhatsAppSignup.
    • Implemented dynamic Facebook SDK loading and initialization (loadFacebookSDK).
    • Added logic to handle the Facebook Login popup, capture the authorization code (authResponse.code), and send it to the backend.
    • Implemented exchangeCodeForToken to handle the backend response.
    • Added UI support for "Pending Registration" state (allowing manual PIN entry later) and success notifications showing the generated 2FA PIN.
    • Added "Test Number" and "Manual Entry" badge support.

Backend Changes

  • New API Endpoint (POST /accounts/exchange-token):

    • Handles the OAuth 2.0 flow by exchanging the authorization code for a long-lived Meta access token.
    • Account Discovery: Implemented intelligent discovery logic to find the WABA ID and Phone Number ID directly from the access token debug info (checking granular scopes) if they are not provided by the frontend.
    • Auto-Registration: The system now attempts to automatically register the phone number with Meta using a random 6-digit PIN immediately upon connection to streamline the user experience.
    • Webhook Subscription: Automatically subscribes the app to webhooks for the discovered WABA to ensure message delivery.
    • Validation: Validates the phone number status and ensures it belongs to the WABA.
  • New API Endpoint (POST /accounts/{id}/register):

    • Allows for manual Two-Step Verification (registration) if the auto-registration fails or if the user needs to set a custom PIN.
  • Infrastructure (pkg/whatsapp/client.go):

    • Added ExchangeCodeForToken to interact with Meta's OAuth endpoints.
    • Added GetTokenDebugInfo for inspecting token scopes and targets.
    • Added RegisterPhoneNumber and SubscribeApp methods to handle the setup actions.

Testing

  • Added internal/handlers/embedded_signup_test.go with comprehensive coverage:
    • TestApp_ExchangeToken_Success_AutoRegistration: Verifies full flow with automatic registration.
    • TestApp_ExchangeToken_Success_CodeOnly_Discovery: Verifies the ID discovery logic works when frontend sends only the auth code.
    • TestApp_RegisterPhone_Success_WithPIN: Tests the manual registration fallback.
    • Error case coverage for invalid codes and unauthorized organization access.

@Saifallak
Copy link
Contributor Author

Saifallak commented Feb 3, 2026

@shridarpatil this would work only with live apps, as what i tested.
i added config endpoint, which we can add more details there later like theme maybe ? any way this endpoint helps the integration here only for now.

p.s: tests are really hard, i tried to mock FB Servers alot. :( ..

@Saifallak
Copy link
Contributor Author

Plans for future:

  • i noticed that you added btn for subscribe endpoint, i'm planning to add another btn for register endpoint in another PR, let me know if you disagree with this.

@shridarpatil
Copy link
Owner

Can you add a video/gif of this?

@shridarpatil
Copy link
Owner

Plans for future:

* i noticed that you added btn for subscribe endpoint, i'm planning to add another btn for register endpoint in another PR, let me know if you disagree with this.

Register button for what?
can you give some more detail?

webhook_verify_token = "random-string-change-this"
api_version = "v24.0"
base_url = "https://graph.facebook.com"
app_id = "" # Meta App ID for Embedded Signup
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this moved to the database?
Otherwise a single embbed phone number could be registered per whatomate instance

Copy link
Contributor Author

@Saifallak Saifallak Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, this is the tech provider or solution provider app
Which users signup and give him access through the embedded signup

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong. You shouldn't be using it for a tech provider. This was meant for the WhatsApp account. You should give an option on the UI to add these details without saving them in the database.

@Saifallak
Copy link
Contributor Author

Saifallak commented Feb 6, 2026

Justification for Embedded Signup Implementation

Click to view more thinking

Executive Summary

The feedback stating that using "Embedded Signup" is "wrong" or suggesting that configuration details should be input via the UI is inaccurate in the context of our operation as a Tech Provider (Business Solution Provider Model).

Our implementation adheres to the official standard recommended by Meta for any platform (SaaS / CRM / Tech Provider) seeking to enable clients to connect their numbers and create WhatsApp Business API accounts directly from within the system.


1. Addressing the Reviewer's Feedback

The reviewer mentioned:

"You should give an option on the UI to add these details without saving them in the database."

This feedback likely stems from a misunderstanding of two distinct integration models. The reviewer might be suggesting that App ID and App Secret should be input by the user in the UI instead of being static in the configuration. This is fundamentally incorrect for a Tech Provider.

Why User-Input for App Config is Wrong:

  1. Tech Provider Identity: In this model, WE are the Tech Provider. The App ID and App Secret belong to OUR Meta App, which has passed Business Verification and App Review.

    • Correct Flow: The user connects their WhatsApp account to our App.
    • Incorrect Flow: Asking the user to input an App ID implies they have their own Meta App. This shifts the complexity back to the user (creating an app, getting verified), effectively destroying the value proposition of our SaaS.
  2. Security Risk: We should never ask a client to paste an App Secret into a frontend UI. If the reviewer means our App Secret should be input via UI to avoid hardcoding, that is a deployment detail (Env Vars), not a UI feature. If they mean the User's App Secret, that implies a "Direct Integration" model, which is legacy and not what we are building.

  3. The "Embedded" Advantage:
    We utilize the Embedded Signup Flow, the solution specifically designed by Meta for Tech Providers.

    From Meta's Documentation: "Embedded Signup is a secure, privacy-safe way for business to share their WhatsApp Business account (WABA) with a Solution Partner (Tech Provider)."

    In this flow, we MUST perform the following steps (as our code does):

    1. Use OUR App Config to initiate the Facebook Login.
    2. Receive the auth_code.
    3. Exchange it for a token.
    4. Store the User's Data: We must store the discovered waba_id, phone_number_id, and access_token in our database.

Refutation: If we do not store this data, we cannot send messages asynchronously (automation/campaigns). If we ask the user to input App details, we are no longer a Tech Provider but merely a basic API client wrapper, forcing them to do all the technical setup themselves.


2. Code Validation & Compliance

I have reviewed our Backend Code (internal/handlers/accounts.go), and it strictly follows the confirmed standards:

  • Platform-Level Config: AppID and AppSecret are correctly loaded from server configuration (Environment Variables). They are Platform Credentials, not User Inputs.
  • User-Level Data: The user does provide their WABA and Phone data, but they do it via the secure, automated OAuth Flow, not by typing IDs into text fields.
  • Automatic Discovery: We leverage Granular Scopes to automatically identify the WABA ID from the token. This is a UX Best Practice to minimize human error.

3. Proposed Response to Reviewer

Report Title: Justification for Embedded Signup & Configuration Strategy

Context:
We are operating as a Tech Provider / SaaS Platform. We broker the connection between the Client's Business and Meta's API using OUR Meta Application.

Response regarding "UI Input for Config":
The suggestion to expose configuration details (like App ID/Secret) in the UI or avoid database storage is incorrect for our model:

  1. App ID & Secret are Platform Credentials: These identify our software to Meta. They are constant for the platform and should not be input by the end-user. Asking the user for these creds would require them to become a developer, create their own App, and undergo Review—negating the benefit of using our platform.
  2. Database Storage is Mandatory: To function as a messaging solution (sending automated notifications, 2FA, marketing messages), we must persist the client's waba_id, phone_number_id, and system_user_token. Without persistence, we cannot make API calls when the user is offline.
  3. Standard Compliance: Our flow matches the Meta Tech Provider Onboarding specification exactly:
    • Provider: Supplies App & Login Flow.
    • User: Grants permission via Facebook Popup.
    • System: Automatically exchanges tokens and stores IDs.

Conclusion:
Moving App Configuration to the UI would convert our "SaaS Solution" into a "Developer Tool," significantly increasing friction and confusing non-technical business users. Our current implementation offers the standard, seamless "Connect with Facebook" experience expected in the industry.

@shridarpatil , you can read more about in meta docs, to use embedded signup is meant to be platform credentials and other people give you access to manage their whatsapps, and to be easy click to give you that access.

here is a video too ( phone is pending registration because it's waiting for our biz to be verified as tech provider, but in testing environments it would say success )

Screen.Recording.2026-02-06.at.2.33.15.PM-2.mp4

P.S: tests are failing now after merging with master, will fix it on sunday.

Copy link
Owner

@shridarpatil shridarpatil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: PR #127 — Feature: Embedded Signup

Overview

This PR adds WhatsApp Embedded Signup flow, allowing users to connect accounts via Facebook OAuth instead of manual credential entry. It spans 12 files (+1859/-13 lines) across backend, frontend, and tests.


Security Issues (Critical)

1. /api/config exposes config publicly (internal/handlers/app.go, cmd/whatomate/main.go)

The /api/config endpoint is added to the public routes bypass list (no auth required). While app_id and config_id aren't strictly secret, exposing a blanket /api/config endpoint publicly sets a bad precedent — any future config additions could accidentally leak sensitive values. Consider:

  • Naming it more specifically (e.g., /api/embedded-signup/config)
  • Only returning values when they're non-empty

2. PIN stored in plaintext in DB (internal/models/models.go)

Pin string `gorm:"size:6" json:"pin"`

The 2FA PIN is stored as plaintext and returned in API responses. The PIN is also exposed via json:"pin" on the model — meaning any endpoint that serializes a WhatsAppAccount could leak it.

3. PIN returned in API response (internal/handlers/accounts.go)

response["pin"] = account.Pin

And in RegisterPhone:

"pin": pin,

The PIN is sent back to the frontend where it's shown in a toast notification for 10 seconds. Consider whether this is necessary after initial setup.


Architecture Issues

4. Config approach (flagged by reviewers)

As noted in the existing review comments — the app_id, app_secret, and config_id are currently in the TOML config file, making this a single-tenant embedded signup setup. For a multi-tenant platform, each organization might want to bring their own Meta App credentials. This is the most significant architectural concern.

5. ExchangeToken does too much (300+ lines)

This single handler does: token exchange, WABA discovery, phone discovery, phone info lookup, account creation/update, auto-registration, webhook subscription, and response building. Consider breaking into smaller functions for readability and testability:

  • discoverWABAAndPhone()
  • createOrUpdateAccount()
  • attemptAutoRegistration()

6. generateVerifyToken() called but not defined in the diff

Line 490 calls generateVerifyToken() but this function isn't in the diff. If it's defined elsewhere, fine — but it should be verified it exists.


Code Quality Issues

7. Excessive debug logging in production code

Both frontend and backend have heavy console.log/a.Log.Info with [FB_SIGNUP] prefix. These are useful during development but should be cleaned up:

  • Frontend: 15+ console.log/console.error/console.warn calls in AccountsView.vue
  • Backend: 20+ log statements with full request/response dumps including token lengths

8. Hardcoded strings in frontend (AccountsView.vue)

<p class="text-lg font-medium ...">No WhatsApp accounts connected</p>
<p class="text-sm ...">Connect your WhatsApp Business account via Facebook...</p>

And:

Connect with Facebook

These should use $t() i18n translations like the rest of the codebase.

9. TestApp_ExchangeToken_MissingFields test has incorrect expectations

The test expects StatusBadRequest for missing phone_id and waba_id, but the handler actually supports discovery mode when these are missing (it proceeds to debug_token flow). Only missing code should return 400. The missing_phone_id and missing_waba_id test cases will hit the mock server which returns 400 for everything, masking the real behavior.

10. RegisterPhone returns 200 with success: false on failure

return r.SendEnvelope(map[string]interface{}{
    "success": false,
    "error":   err.Error(),
})

This returns HTTP 200 with an error body, which is inconsistent with the rest of the codebase that uses SendErrorEnvelope with proper HTTP status codes.

11. E2E test change may be premature (AccountsPage.ts)

-this.addButton = page.getByRole('button', { name: /Add Account/i }).first()
+this.addButton = page.getByRole('button', { name: /Manual Entry/i }).first()

The "Add Account" button text hasn't actually changed in the template — it still uses {{ $t('accounts.addAccount') }}. The e2e locator change seems based on an unreleased UI change.

12. Modulo bias in PIN generation

b[i] = (b[i] % 10) + '0'

Using % 10 on a random byte (0-255) introduces slight bias: digits 0-5 are ~2% more likely than 6-9. Minor for a 6-digit PIN but could use crypto/rand integer generation for correctness.

13. Fallback PIN is deterministic

if _, err := rand.Read(b); err != nil {
    return "123456" // Fallback
}

If the CSPRNG fails, all accounts get PIN "123456". Better to return an error instead.


Summary

The feature is well-structured with good test coverage (comprehensive mock servers, cross-org isolation tests, error cases). Main concerns:

  1. Must fix: Hardcoded English strings should use i18n
  2. Must fix: RegisterPhone should use proper HTTP error codes
  3. Should fix: Clean up debug logging before merge
  4. Should fix: PIN storage/exposure needs review
  5. Architectural: The TOML config approach limits multi-tenancy (per existing review comments)
  6. Consider: Break up the 300-line ExchangeToken handler

@Saifallak Saifallak mentioned this pull request Feb 12, 2026
@spss20
Copy link

spss20 commented Feb 23, 2026

When can we expect it to be merged into main branch?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants