react-whatsapp-embedded-signup
v1.4.0
Published
React hook, button, and manual OAuth helpers for Meta WhatsApp Embedded Signup (SDK popup, URL builder, popup flow)
Maintainers
Readme
react-whatsapp-embedded-signup
React hook, button, and helpers for Meta’s WhatsApp Embedded Signup — onboard a WhatsApp Business Account (WABA) without wiring the Facebook SDK by hand.
Supports:
- JS SDK popup —
FB.loginwithconfig_id - OAuth URL — build
facebook.com/.../dialog/oauthlinks yourself - OAuth popup — open that URL in a popup, capture the auth
code, close popup, callonSuccess
Install
npm install react-whatsapp-embedded-signupyarn add react-whatsapp-embedded-signuppnpm add react-whatsapp-embedded-signupPeer dependency: react / react-dom >= 16.8.
Dummy credentials (examples below)
Use these placeholder values in docs and local testing — replace with your real Meta app values.
| Key | Dummy value |
|-----|-------------|
| App ID | 1191095509611003 |
| Config ID | 3847291056382741 |
| App Secret | a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 (server only) |
| API version | v25.0 |
| OAuth state | signup-7f3a9c |
Example onSuccess payload:
{
"code": "AQBx7k2mN9pQwR4sT6uV8wX0yZ1aB3cD5eF7gH9iJ0kL2mN4oP6qR8sT",
"redirectUri": "https://app.acme-corp.com/settings/whatsapp",
"sessionInfo": {
"waba_id": "123456789012345",
"phone_number_id": "987654321098765",
"business_id": "112233445566778"
}
}What it does
- Loads the Facebook JavaScript SDK once and initializes it with your App ID
- Opens embedded signup via
FB.login(..., { config_id })or an OAuth URL - Listens for
WA_EMBEDDED_SIGNUPpostMessageevents (waba_id,phone_number_id,business_id) - Returns a short-lived authorization
codeplus the matchingredirectUrifor your backend token exchange - Checks
FB.getLoginStatuswhen the SDK is ready - OAuth popup: closes on redirect, captures
code, always passes it toonSuccess
Meta prerequisites
- Create a Meta App and add the WhatsApp product.
- Under WhatsApp → Embedded Signup, create a configuration → copy
config_id. - Add your URLs under Valid OAuth Redirect URIs, e.g.:
http://localhost:5173/https://app.acme-corp.com/settings/whatsapp
- Keep App ID in the client; keep App Secret on the server only.
Usage — button
import { WhatsAppEmbeddedSignupButton } from 'react-whatsapp-embedded-signup';
function ConnectWhatsApp() {
return (
<WhatsAppEmbeddedSignupButton
appId="1191095509611003"
configId="3847291056382741"
onSuccess={async ({ code, sessionInfo, redirectUri }) => {
// code is always a non-empty string
await fetch('/api/whatsapp/connect', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
code,
redirectUri,
wabaId: sessionInfo?.waba_id, // "123456789012345"
phoneNumberId: sessionInfo?.phone_number_id, // "987654321098765"
}),
});
}}
onError={(err) => console.error(err.code, err.message, err.details)}
>
Connect WhatsApp Business
</WhatsAppEmbeddedSignupButton>
);
}Usage — hook (JS SDK popup)
import { useWhatsAppEmbeddedSignup } from 'react-whatsapp-embedded-signup';
function SdkSignup() {
const { launchSignup, isSdkReady, isLaunching } = useWhatsAppEmbeddedSignup({
appId: '1191095509611003',
configId: '3847291056382741',
apiVersion: 'v25.0',
onSuccess: ({ code, sessionInfo, redirectUri }) => {
console.log(code); // "AQBx7k2mN9pQwR4sT6uV8wX0yZ1aB3cD5eF7gH9iJ0kL2mN4oP6qR8sT"
console.log(redirectUri); // "https://staticxx.facebook.com/x/connect/xd_arbiter/?version=46"
console.log(sessionInfo?.waba_id); // "123456789012345"
},
onError: (err) => alert(err.message),
onEvent: (raw) => console.debug('signup step', raw),
});
return (
<button type="button" onClick={launchSignup} disabled={!isSdkReady || isLaunching}>
Connect WhatsApp
</button>
);
}Usage — hook (OAuth popup)
Opens OAuth in a popup, closes on redirect, captures code, calls onSuccess:
import { useWhatsAppEmbeddedSignup } from 'react-whatsapp-embedded-signup';
function OAuthPopupSignup() {
const { launchOAuth, isLaunching } = useWhatsAppEmbeddedSignup({
appId: '1191095509611003',
configId: '3847291056382741',
nav: 'popup',
state: 'signup-7f3a9c',
// redirectUri defaults to current page URL — works on any host/path
onSuccess: ({ code, redirectUri, sessionInfo }) => {
fetch('/api/whatsapp/connect', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code, redirectUri, ...sessionInfo }),
});
},
onError: (err) => alert(err.message),
});
return (
<button type="button" onClick={launchOAuth} disabled={isLaunching}>
Connect via OAuth popup
</button>
);
}Usage — launchOAuthPopup (one call)
import { launchOAuthPopup } from 'react-whatsapp-embedded-signup';
const stop = launchOAuthPopup({
appId: '1191095509611003',
configId: '3847291056382741',
state: 'signup-7f3a9c',
onDone: ({ callback, redirectUri }) => {
// callback.code → "AQBx7k2mN9pQwR4sT6uV8wX0yZ1aB3cD5eF7gH9iJ0kL2mN4oP6qR8sT"
if (callback.code) {
exchangeOnServer(callback.code, redirectUri);
}
},
onError: (err) => console.error(err.message),
});
// stop() to cancel watchingUsage — build OAuth URL
import { buildOAuthUrl, resolveRedirectUri } from 'react-whatsapp-embedded-signup';
const url = buildOAuthUrl({
appId: '1191095509611003',
configId: '3847291056382741',
redirectUri: resolveRedirectUri(), // e.g. "https://app.acme-corp.com/settings/whatsapp"
state: 'signup-7f3a9c',
});
// https://www.facebook.com/v25.0/dialog/oauth?client_id=1191095509611003&redirect_uri=...Usage — OAuth popup (manual)
import { openOAuthPopup, watchOAuthPopup } from 'react-whatsapp-embedded-signup';
const { popup, redirectUri } = openOAuthPopup({
appId: '1191095509611003',
configId: '3847291056382741',
state: 'signup-7f3a9c',
});
watchOAuthPopup({
popup,
redirectUri,
onDone: ({ callback, redirectUri: uri }) => {
if (callback.code) exchangeOnServer(callback.code, uri);
},
onError: (err) => console.error(err.message),
});Usage — OAuth full-page redirect
import { startOAuth, parseOAuthCallback } from 'react-whatsapp-embedded-signup';
startOAuth({
appId: '1191095509611003',
configId: '3847291056382741',
redirectUri: 'https://app.acme-corp.com/settings/whatsapp',
state: 'signup-7f3a9c',
nav: 'replace',
});
// On return:
const result = parseOAuthCallback();
if (result?.code) {
exchangeOnServer(result.code, 'https://app.acme-corp.com/settings/whatsapp');
}Backend: exchange the code
GET https://graph.facebook.com/v25.0/oauth/access_token
?client_id=1191095509611003
&client_secret=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
&redirect_uri=https://app.acme-corp.com/settings/whatsapp
&code=AQBx7k2mN9pQwR4sT6uV8wX0yZ1aB3cD5eF7gH9iJ0kL2mN4oP6qR8sTExample response:
{
"access_token": "EAABsbCS1iHgBO7ZC8ZAZB4vZC...",
"token_type": "bearer",
"expires_in": 5184000
}| Flow | redirect_uri to send |
|------|------------------------|
| SDK popup | From onSuccess.redirectUri — Facebook xd_arbiter URL by default |
| OAuth popup / redirect | From onSuccess.redirectUri — your page URL, e.g. https://app.acme-corp.com/settings/whatsapp |
Server helper (auto-detect current page from request headers):
import { redirectFromReq } from 'react-whatsapp-embedded-signup';
// POST /api/exchange-token { "code": "AQBx7k2m..." }
const redirectUri = redirectFromReq(req.headers);Never expose your App Secret in the browser.
API
useWhatsAppEmbeddedSignup(options)
| Option | Type | Description |
|--------|------|-------------|
| appId | string | Meta App ID, e.g. 1191095509611003 |
| configId | string | Embedded Signup config ID, e.g. 3847291056382741 |
| apiVersion | string | Default v25.0 (DEFAULT_API_VERSION) |
| extras | object | Merged into FB.login extras.setup |
| exchangeUri | string | SDK token-exchange URI; defaults to Facebook xd_arbiter static URL |
| redirectUri | string | OAuth dialog redirect URI; defaults to current page URL |
| state | string | OAuth state param, e.g. signup-7f3a9c |
| nav | 'popup' \| 'replace' \| 'assign' | How launchOAuth opens the dialog; default popup |
| onSuccess | (result) => void | { code, sessionInfo, redirectUri } — code is always present |
| onError | (error) => void | Includes code / details when set |
| onEvent | (event) => void | Every raw WA_EMBEDDED_SIGNUP message |
Returns:
| Property | Type | Description |
|----------|------|-------------|
| launchSignup | () => void | JS SDK popup (FB.login) |
| launchOAuth | () => void | OAuth popup (default) or full-page redirect |
| isSdkReady | boolean | Facebook SDK loaded |
| isLaunching | boolean | Signup in progress |
| loginStatus | string \| null | FB.getLoginStatus status |
| authResponse | object \| null | Latest auth response |
| checkLoginStatus | () => void | Re-run FB.getLoginStatus |
| isCheckingLoginStatus | boolean | Status check in flight |
WhatsAppEmbeddedSignupButton
Same options as the hook, plus standard button props (className, style, disabled, children, …).
OAuth helpers
| Export | Description |
|--------|-------------|
| buildOAuthUrl | Build facebook.com/{version}/dialog/oauth?... |
| launchOAuthPopup | Open popup + watch + close + return code |
| openOAuthPopup | window.open the OAuth URL |
| watchOAuthPopup | Watch popup until code or close |
| startOAuth | Open OAuth flow (popup, replace, or assign) |
| parseOAuthCallback | Parse ?code= / #code= / ?error= from URL |
| relayPopupToOpener | Callback page: post code to opener (cross-origin safe) |
| resolveCallbackRedirectUri | Callback page: rebuild exact redirect_uri from current URL |
| isTrustedOAuthMessageOrigin | Validate OAuth postMessage origin |
| oauthRedirectOrigin | Origin of a redirect URI |
| resolveRedirectUri | Current page URL (origin + pathname) |
| normalizeRedirectUri | Normalize any URL to OAuth redirect_uri shape |
| resolveExchangeUri | SDK token-exchange redirect URI |
| redirectFromReq | Resolve redirect URI from HTTP request headers |
| FB_STATIC_URI | Facebook JS SDK xd_arbiter URL |
| DEFAULT_API_VERSION | v25.0 |
Notes
- Auth codes expire in ~30 seconds — exchange on your server immediately.
onSuccessalways receives a non-emptycode; otherwiseonErrorfires withNO_AUTH_CODE.onErroralso fires for user cancel (err.code === 'CANCELLED').- OAuth
redirect_uridefaults to the current page — works on any URL; register each URL in Meta. - OAuth popup requires popups allowed for your domain.
- Cross-origin callback — opener and callback can be different domains. The callback relays the
codeviapostMessageto the opener. Register the exact redirect URI in Meta.
Cross-origin callback (e.g. API domain)
Opener (admin app):
const { launchOAuth } = useWhatsAppEmbeddedSignup({
appId,
configId,
redirectUri: 'https://test-admin-api.wayvida.com/token/callback',
deferRelay: true, // callback handles API first, then relays manually
onSuccess: ({ code, redirectUri }) => {
console.log('Got code in opener', code, redirectUri);
},
});Callback page (https://test-admin-api.wayvida.com/token/callback):
import { parseOAuthCallback, relayPopupToOpener, resolveCallbackRedirectUri } from 'react-whatsapp-embedded-signup';
const result = parseOAuthCallback();
const redirectUri = resolveCallbackRedirectUri();
async function finish() {
try {
await fetch('/api/exchange-token', {
method: 'POST',
body: JSON.stringify({ code: result?.code, redirectUri }),
});
// show success UI
} catch {
// show error UI
}
relayPopupToOpener({ close: false }); // send code to opener admin app
setTimeout(() => window.close(), 10_000);
}
if (result?.code) void finish();Without deferRelay, the callback auto-relays and closes immediately on load.
License
MIT
