Merge pull request #252 from ArnasDon/fix/whatsapp-test-number-pin

fix(whatsapp): allow connecting Meta test numbers without a PIN
This commit is contained in:
Arnas Donauskas 2026-06-15 17:04:11 +03:00 committed by GitHub
commit f36cd23123
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 58 additions and 35 deletions

View File

@ -284,41 +284,48 @@ export async function POST(request: Request) {
// Step 1: register the phone number for inbound webhooks.
//
// Required on first save AND whenever the user supplies a fresh
// Attempted on first save AND whenever the user supplies a fresh
// PIN (e.g. they rotated the 2FA PIN in Meta Manager). Skipped
// when the same number is already registered and no PIN was
// supplied — re-registering an already-active number with a
// stale PIN would actually fail and undo the active subscription.
let registeredAt: string | null = existing?.registered_at ?? null
let registrationError: string | null = null
// True when registration was deliberately skipped because no PIN
// was supplied (see below). Distinct from registrationError — this
// is not a failure, just an incomplete-but-valid save.
let registrationSkipped = false
const needsRegistration = !sameNumber || (typeof pin === 'string' && pin.length > 0)
if (needsRegistration) {
if (!pin) {
return NextResponse.json(
{
error:
'Two-step verification PIN is required to subscribe this number to wacrm. ' +
'Set a 6-digit PIN in Meta WhatsApp Manager → Phone Numbers → Two-step verification, then paste it below.',
},
{ status: 400 }
)
}
try {
await registerPhoneNumber({
phoneNumberId: phone_number_id,
accessToken: access_token,
pin,
})
registeredAt = new Date().toISOString()
} catch (err) {
registrationError =
err instanceof Error ? err.message : 'Unknown Meta API error'
console.error('Phone number /register failed:', registrationError)
// We deliberately fall through and still save the row so the
// user can retry without re-entering everything. The UI
// surfaces `last_registration_error` so they see WHY it's
// not actually live yet.
// No PIN provided. Meta TEST numbers (Developer Console) are
// pre-registered by Meta and expose no two-step verification
// PIN to set, so requiring one made them impossible to connect
// (issue #242). The /register + PIN step only matters for
// production numbers under a shared WABA (issue #136), so treat
// it as best-effort: skip it, save the (already Meta-verified)
// credentials as connected, and leave registered_at null. The
// UI surfaces a separate "Not registered" banner with a path to
// add a PIN later for users who do need inbound webhook routing.
registrationSkipped = true
} else {
try {
await registerPhoneNumber({
phoneNumberId: phone_number_id,
accessToken: access_token,
pin,
})
registeredAt = new Date().toISOString()
} catch (err) {
registrationError =
err instanceof Error ? err.message : 'Unknown Meta API error'
console.error('Phone number /register failed:', registrationError)
// We deliberately fall through and still save the row so the
// user can retry without re-entering everything. The UI
// surfaces `last_registration_error` so they see WHY it's
// not actually live yet.
}
}
}
@ -410,7 +417,12 @@ export async function POST(request: Request) {
return NextResponse.json({
success: true,
saved: true,
registered: true,
registered: registeredAt != null,
// Credentials are valid and saved, but inbound webhook
// registration was skipped because no PIN was supplied (e.g. a
// Meta test number). The UI shows the "Not registered" banner
// rather than claiming the number is fully live.
registration_skipped: registrationSkipped,
phone_info: phoneInfo,
})
} catch (error) {

View File

@ -233,6 +233,16 @@ export function WhatsAppConfig() {
`Saved, but Meta couldn't register the number: ${data.registration_error}`,
{ duration: 12000 },
);
} else if (data.registration_skipped) {
// Credentials saved + verified, but /register was skipped
// because no PIN was supplied (e.g. a Meta test number).
// Don't claim the number is "Live" — point at the
// Registration status banner instead.
toast.success(
'Credentials saved and verified. Inbound registration was skipped (no PIN) — see Registration status below.',
{ duration: 10000 },
);
setPin('');
} else {
toast.success(
data.phone_info?.verified_name
@ -604,9 +614,7 @@ export function WhatsAppConfig() {
<div className="space-y-2">
<Label className="text-slate-300">
Two-step verification PIN
{!isRegistered && (
<span className="ml-1 text-red-400">*</span>
)}
<span className="ml-1 text-slate-500">(optional)</span>
</Label>
<Input
type="text"
@ -620,16 +628,19 @@ export function WhatsAppConfig() {
className="bg-slate-800 border-slate-700 text-white placeholder:text-slate-500 tracking-widest"
/>
<p className="text-xs text-slate-500 leading-relaxed">
Required the first time you connect a number, and any
time you swap to a different number. Set it in{' '}
Needed only to wire <strong className="text-slate-300">inbound</strong> messages
for a <strong className="text-slate-300">production</strong> number. Set it in{' '}
<strong className="text-slate-300">
Meta Business Manager → WhatsApp Accounts → Phone
Numbers → Two-step verification
</strong>
. Without this PIN, Meta saves your credentials but
won&apos;t actually route inbound messages to wacrm —
the symptom that hits second numbers under a shared
WABA. Leave blank to keep an existing registration
, then paste it here so wacrm can subscribe the number —
otherwise Meta routes inbound events to whichever app
last claimed it (the symptom that hits second numbers
under a shared WABA).{' '}
<strong className="text-slate-300">Meta test numbers</strong> have no
PIN and are pre-registered — leave this blank for them.
Leaving it blank also keeps an existing registration
untouched.
</p>
</div>