The Login for Business wizard says login variation and the product set cannot be changed later. You can still tick every WhatsApp-related product. The form accepts it. Then those boxes are dead.
That is not a reminder to finish later. It is a freeze. A “complete” list today is how you throw the configuration away tomorrow.
Invariant: if the vendor marks a create-time knob immutable, that knob is the identity of the configuration object — treat it like a migration, not a preference screen.
Why “select all for later” lies
The wrong model is: I’ll tick every WhatsApp product now so we don’t come back to the dashboard.
Login variation is identity, not a later setting. General and WhatsApp Embedded Signup are different objects. You do not flip one into the other. You recreate.
Product set is the same freeze, and it is coupling. Extra products pull extra assets, and review follows the list. Cloud API alone is a complete v1 for this path. Ads assets belong to a later config, not this one.
We reject an over-coupled create as v1 policy. The wizard would likely accept Cloud API plus Click-to-WhatsApp plus Conversions API, then freeze that mix. That is why “select all” is not completeness. It is a migration you did not mean to run.
Lab: four rows, one freeze
I ran node lab/login-config-freeze.mjs in this repo on 13 August 2026. In-process factory. No network.
| Case | Create | What we need | Result |
|---|---|---|---|
| A | General + Cloud API | reject wrong object | ok: false, reason: "wrong_variation" |
| B | WA Embedded Signup + Cloud API | complete v1, then freeze | ok: true, frozen: true, productCount: 1 |
| C | same variation + Cloud + CTWA + CAPI | v1 policy fail (over-coupled) | ok: false, reason: "over_coupled" |
| D | after B, add Marketing Messages | mutation disallowed | accepted: false, reason: "immutable" |
Trimmed report:
{
"A": { "ok": false, "reason": "wrong_variation" },
"B": {
"ok": true,
"cloudOnboardV1": true,
"frozen": true,
"productCount": 1
},
"C": { "ok": false, "reason": "over_coupled" },
"D": {
"accepted": false,
"reason": "immutable",
"productCountAfter": 1
},
"allPassed": true
}
The interesting failure is not that D returns immutable. It is that C is our policy, not a vendor 400. The wizard would likely mint that object and freeze it. A is the wrong variation — a different identity, not a missing product. B is the only create that is complete for v1 and then frozen.
function createConfig({ variation, products }) {
if (variation !== V1_VARIATION) {
return { ok: false, reason: "wrong_variation" };
}
if (!sameSet(products, V1_PRODUCTS)) {
return { ok: false, reason: "over_coupled" }; // our policy — wizard would still mint
}
return { ok: true, frozen: true }; // never grow the product set
}
Decision boundary
Rejected: ticking every WhatsApp-related product “so we don’t come back,” and treating login variation as a later setting.
Need a second product later? Create another configuration. Do not patch this one. Access-token preferences are not this freeze.
Filling env after the id exists is a sibling problem; this note stops at create-time identity.
What this does not solve
Vendor App Review. Env reachability after the id exists — a missing config id fails only the path humans click. Whether someone finishes the wizard end to end (enabled flags can stay false until they do).
Create-time knobs the vendor marks immutable are a migration: review once, freeze. Boring on purpose. A complete checkbox list is not a backlog.