/** Default Graph API version used for every call. Override with `graphBase`. */ export declare const DEFAULT_GRAPH_BASE = "https://graph.facebook.com/v22.0"; /** OAuth scopes Embedded Signup needs. */ export declare const DEFAULT_SIGNUP_SCOPES: readonly ["whatsapp_business_management", "whatsapp_business_messaging", "business_management"]; /** Default lifetime of a signed `state` value. */ export declare const DEFAULT_STATE_TTL_SECONDS: number; export type EmbeddedSignupErrorCode = 'invalid_state' | 'token_exchange_failed' | 'waba_lookup_failed' | 'no_waba_found' | 'phone_lookup_failed' | 'no_phone_number' | 'asset_lookup_failed' | 'no_authorized_assets' | 'asset_not_authorized' | 'subscribe_failed' | 'register_failed'; /** * Every failure path throws this. Branch on `code` — the strings are stable * API, the messages are not. */ export declare class EmbeddedSignupError extends Error { readonly code: EmbeddedSignupErrorCode; /** HTTP status from Graph, when the failure came from a call. */ readonly status: number | null; /** Parsed Graph error body, when there was one. */ readonly detail: unknown; constructor(code: EmbeddedSignupErrorCode, message: string, status?: number | null, detail?: unknown); } export interface Waba { id: string; name: string | null; } export interface SignupPhoneNumber { id: string; displayPhoneNumber: string | null; verifiedName: string | null; qualityRating: string | null; } /** * A (WABA, phone number) pair the **exchanged token** actually authorizes. * This is the allowlist `complete()` validates caller-supplied ids against — * see `listAuthorizedAssets`. */ export interface AuthorizedAsset { wabaId: string; wabaName: string | null; phoneNumberId: string; displayPhoneNumber: string | null; verifiedName: string | null; } export interface MetaEmbeddedSignupOptions { appId: string; appSecret: string; /** * Must match the redirect URI registered on the Meta app. Required for * the OAuth flow; the JS SDK flow still sends it on the code exchange, * so pass the same value you configured there (or `''` when unused). */ redirectUri?: string; /** * HS256 secret for signing the `state` round-trip. Set it and `complete()` * REQUIRES a valid state (CSRF protection for the redirect flow). Leave * it unset for the JS SDK flow, where the SDK owns the session. */ stateSecret?: string; /** * Token used for WABA-scoped calls (subscribe, phone lookup, register). * Tech Providers typically use a System User token here. Defaults to the * token returned by the code exchange. */ systemUserToken?: string; /** Graph API base including version. Default `DEFAULT_GRAPH_BASE`. */ graphBase?: string; /** OAuth scopes. Default `DEFAULT_SIGNUP_SCOPES`. */ scopes?: readonly string[]; /** Signed-state lifetime in seconds. Default 600. */ stateTtlSeconds?: number; /** Injectable fetch for tests. */ fetchImpl?: typeof fetch; /** Injectable clock for tests. */ now?: () => number; } export interface CompleteSignupParams { /** One-time code from `FB.login` or the OAuth redirect. */ code: string; /** Signed state from the redirect. Required when `stateSecret` is configured. */ state?: string; /** * WABA id hint from `sessionInfoResponse`. **Verified** against what the * exchanged token authorizes — a hint, never a grant. */ wabaId?: string; /** * Phone number id hint from `sessionInfoResponse`. **Verified** against what * the exchanged token authorizes — a hint, never a grant. */ phoneNumberId?: string; /** * Skip the authorization check on `wabaId` / `phoneNumberId` (v0.19.1). * * **Leave this false.** The ids reach your backend from a browser; trusting * them lets any authenticated user of your app point their tenant at another * business's number and take over its inbox. Set it only when the ids come * from a source you control end-to-end and you've accepted that risk. */ trustSuppliedAssets?: boolean; /** Skip the `POST /{waba}/subscribed_apps` call. Default false. */ skipSubscribe?: boolean; /** * 6-digit PIN for two-step verification. When set, the number is * registered with the Cloud API (`POST /{phone}/register`). Numbers * migrating from the WhatsApp Business app usually need this. */ registerPin?: string; } export interface EmbeddedSignupResult { /** Payload you signed into `authUrl`, or null on the JS SDK flow. */ statePayload: string | null; accessToken: string; wabaId: string; phoneNumberId: string; displayPhoneNumber: string | null; verifiedName: string | null; subscribed: boolean; registered: boolean; /** WABAs seen during discovery. Empty when `wabaId` was supplied. */ wabas: Waba[]; /** Phone numbers seen during discovery. Empty when `phoneNumberId` was supplied. */ phoneNumbers: SignupPhoneNumber[]; /** * Everything the exchanged token authorized (v0.19.1). Empty only when * `trustSuppliedAssets` skipped the check. Persist-worthy: it's the proof * the connecting user actually owns the number you're about to route to * their tenant. */ authorizedAssets: AuthorizedAsset[]; } export declare class MetaEmbeddedSignup { readonly appId: string; readonly graphBase: string; readonly scopes: readonly string[]; readonly stateTtlSeconds: number; private readonly appSecret; private readonly redirectUri; private readonly stateSecret; private readonly systemUserToken; private readonly fetchImpl; private readonly now; constructor(opts: MetaEmbeddedSignupOptions); /** * Facebook OAuth dialog URL for the redirect flow, with `statePayload` * signed into the `state` parameter. Throws when no `stateSecret` was * configured — an unsigned state is a CSRF hole, not a convenience. */ authUrl(statePayload: string): Promise; /** Sign a state payload (HS256 + expiry + nonce). */ signState(payload: string): Promise; /** * Verify a state produced by `signState`. Returns the payload, or `null` * when the signature is wrong, the token is malformed, or it expired. */ verifyState(state: string | undefined | null): Promise; /** Exchange the one-time code for an access token. */ exchangeCode(code: string): Promise<{ accessToken: string; tokenType: string | null; expiresIn: number | null; }>; /** * Enumerate the (WABA, phone number) pairs **this exact token** authorizes * (v0.19.1). This is the allowlist `complete()` validates caller-supplied * ids against. * * Pass the token from the code exchange, and only that one. A System User * token sees every number in the app, so enumerating with it proves nothing * about who is connecting — which is exactly the hole this closes. The * method therefore never falls back to `systemUserToken`. */ listAuthorizedAssets(exchangedToken: string): Promise; /** WABAs owned by the businesses the token can see. */ listWabas(token: string): Promise; /** Phone numbers registered under a WABA. */ listPhoneNumbers(wabaId: string, token?: string): Promise; /** * Subscribe the app to the WABA's webhooks. Without this the tenant is * connected but no message ever reaches your webhook. */ subscribeApp(wabaId: string, token?: string): Promise; /** * Register the number with the Cloud API. Required for numbers migrating * off the WhatsApp Business app; `pin` is the two-step verification code. */ registerPhoneNumber(phoneNumberId: string, pin: string, token?: string): Promise; /** * Run the whole handshake. Supply `wabaId` / `phoneNumberId` from the JS * SDK's session info to skip discovery; omit them and they're looked up. * * Throws `EmbeddedSignupError` on every failure path — see the `code` * taxonomy. Nothing is persisted; the result is yours to store. */ complete(params: CompleteSignupParams): Promise; /** * Token for operational calls (subscribe, register). The System User token * wins when configured — it's long-lived and scoped to your app, which is * what Tech Providers want for calls made after authorization is settled. * * Never use this for authorization checks: see `listAuthorizedAssets`. */ private tokenFor; private graphJson; } /** * Resolve a hint against the authorized set (v0.19.1). Pure logic, exported so * apps that run their own connect endpoint get the same matching rules. * * - both ids given → the exact pair must be authorized * - phone only → that number must be authorized, under any WABA * - WABA only → first authorized number under that WABA * - neither → first authorized pair (the discovery path) * * Returns `null` when nothing matches — the caller turns that into * `asset_not_authorized`. */ export declare function selectAuthorizedAsset(assets: readonly AuthorizedAsset[], hint?: { wabaId?: string; phoneNumberId?: string; }): AuthorizedAsset | null; //# sourceMappingURL=embedded_signup.d.ts.map