64 "https://dev.wix.com/docs/api-reference/crm/members-contacts/members/member-management/members/create-member: when you create a member, a contact is also created; the response carries member.contactId.",
65 "LIVE 2026-09-06 (ensureMember, crm/contact pitfalls): Create Member reuses an existing contact matched by email or phone, so the outcome is create-or-match; new versus matched stays unknown unless evidence distinguishes it. Query Members by loginEmail lags about 5s while the duplicate check is immediate (409).",
66 "Ownership is verified by reading the member back (member.contactId equals the recorded contact) and reading that contact."
67 ]
68 }
69 },
70 "sourceAliases": [
71 {
72 "sourceSystem": "wordpress",
73 "sourceEntity": "user",
74 "routes": [
75 "/wp/v2/users"
76 ],
77 "confidence": "medium"
78 },
79 {
80 "sourceSystem": "woocommerce",
81 "sourceEntity": "customer",
82 "routes": [
83 "/wc/v3/customers"
84 ],
85 "confidence": "medium"
86 },
87 {
88 "sourceSystem": "wix",
89 "sourceEntity": "member",
90 "routes": [
91 "Members APIs"
92 ],
93 "confidence": "high"
94 }
95 ],
96 "preferredWrite": {
97 "surface": "Members V1",
98 "endpoint": "POST /members/v1/members",
99 "writerId": "createMember",
100 "verification": "verified-live",
101 "importSafe": true,
102 "bulk": false
103 },
104 "contactFields": [
105 {
106 "kind": "email",
107 "targetPath": "member.loginEmail",
108 "source": "target-schema",
109 "notes": "Members V1 login email in the create-member request body."
110 }
111 ],
112 "fallbacks": [
113 {
114 "classification": "crm",
115 "when": "Use Contacts for person records that do not need login identity."
116 },
117 {
118 "classification": "cms",
119 "when": "Use for author profiles or member metadata not represented by Members APIs."
120 }
121 ],
122 "reliability": {
123 "status": "unreliable",
124 "flags": [
125 "IMPORT_UNRELIABLE"
126 ]
127 },
128 "pitfalls": [
129 {
130 "code": "member-login-email-domain-rejected",
131 "severity": "warning",
132 "summary": "VERIFIED LIVE 2026-08-16: POST /members/v1/members rejects a `@wix.com` loginEmail with 400 INVALID_EMAIL_DOMAIN, although Contacts V5 accepts that domain. Never mint a synthetic member email on @wix.com. The reserved `.invalid` TLD was accepted by other surfaces in the same probe session but is NOT live-verified for Create Member: the first synthetic author or fallback member a run creates must record whether Members accepted it."
133 },
134 {
135 "code": "passwords-never-imported",
136 "severity": "warning",
137 "summary": "Passwords are NOT imported — project decision (2026-08-03), not just an API gap. Members keep account/profile/contact linkage; they set a new password via reset. Never propose hash import."
138 },
139 {
140 "code": "activation-via-forgot-password",
141 "severity": "warning",
142 "summary": "Activation (decided 2026-08-03): passwordless members complete the standard forgot-password flow (confirmed). Delivery = post-import automation (label-added trigger, owner-branded email, label applied in API waves). Never mass-send Send Set Password Email — its link dies in 3h."
143 },
144 {
145 "code": "serial-create-only",
146 "severity": "warning",
147 "summary": "No bulk member create; documented >=1s spacing per Create Member call (10k members ~ 3h serial floor). Plan throughput and resume via loginEmail crosswalk."
148 },
149 {
150 "code": "chronology-server-controlled",
151 "severity": "warning",
152 "summary": "Original createdDate and lastLoginDate are not writable as native member chronology."
157 "summary": "VERIFIED LIVE 2026-09-06: contact.phones[] REJECTS anything not already E.164 (\"050-1234567\" 400s INVALID_PHONE; \"+972501234567\" is accepted) -- WooCommerce phones are local-format text and need converting or dropping, never sent as-is. Separately, Contacts dedupes on phone: creating a SECOND member with an unrelated name/email but the SAME phone as a first member returned the FIRST member's contactId -- silently attaching the new person onto the old contact, renaming it in the process -- and this happened even after the first member had been DELETED (deleting a member does not delete its contact; the contact is left orphaned and reusable). rp-target-wix/lib/customer-member.js normalizePhoneE164 converts under an explicit per-country rule or refuses (never guesses); wix-writers.js ensureMember queries Contacts V5 by phone before every create (every phone in the array, not just the first -- an earlier version of this guard checked only phones[0], which a review caught 2026-09-06) and drops the phone(s) (finding member-phone-collision-dropped) rather than risk merging two different source customers. Also cosmetic, not a defect: Members V1 read-back reformats the phone with spaces/dashes for display (\"+972 50-999-0001\"), while Contacts V5 stores the compact string sent and exposes it again verbatim under phone.e164 -- compare on digits, not the display string."
162 "summary": "VERIFIED LIVE 2026-09-06: a freshly created member is NOT immediately findable through Query Members by loginEmail -- one probe saw zero matches at +0ms and +2000ms after Create Member returned, then one match at +5000ms. Create Member's own loginEmail UNIQUENESS check, by contrast, is immediate and case-insensitive (a same-email create in a different case 409s ALREADY_EXISTS right away, with no delay observed). So a caller that reconciles by Query Members before creating (rp-target-wix/lib/wix-writers.js ensureMember) can, within that window, see zero matches for a member that in fact already exists on the target (created moments earlier by an interrupted or concurrent run) and then hit a 409 attempting to create it again. ensureMember reports this as its own outcome (member-already-exists-unreconciled) rather than the generic member-failed, since the record is not lost -- a later run's query will catch up and reconcile it -- but it is not a silent success either. Reconciling through a crosswalk row's GET-by-id (matchedBy: crosswalk) does not depend on this: a fresh site's crosswalk-driven re-run in this project's own live verification reconciled correctly immediately after creation, suggesting GET-by-id does not carry the same lag, though that was not isolated and confirmed the way this consistency gap was."
167 "summary": "LIVE-VERIFIED 2026-09-16: once a Contact already has a Member on it (contact.memberInfo present), Contacts' updateContact 428s CANNOT_UPDATE_MEMBER_EMAIL on ANY email PATCH to that contact -- even when the on-file email is still a safe-mode mock. Update Member's own docs independently confirm loginEmail is immutable there too (\"The privacyStatus and loginEmail field can't be updated\"). So if a Member is ever created against the wrong email (a mock address, a typo, a placeholder used for early testing before the real value was known), there is NO supported API path to correct it afterward -- not via Contacts, not via Members. The only paths that avoid this are: (1) never create the Member until the contact's real email is already on file (this is why the crm/contact promotion flow updates the Contact's email BEFORE calling ensureMember, never after), or (2) if a wrong-email member already exists, accept it as-is and treat correcting it as requiring a delete-and-recreate (which loses member history/UGC) rather than an in-place fix. Detect this BEFORE attempting an email update by checking the read contact's own memberInfo.memberId -- it is free on the same getContact call that would otherwise 428 -- rather than discovering it from the write's error."
172 "summary": "LIVE-FOUND: in a real large sequential Create Member batch, with the documented >=1s floor already enforced, a small fraction of otherwise-valid Create Member calls failed on the first attempt with no 4xx/validation signature, and succeeded immediately when the IDENTICAL call was retried moments later with no changes to the request -- not a rate-limit artifact of this caller (the floor was already respected) and not a validation problem. ensureMember (rp-target-wix/lib/wix-writers.js) now retries Create Member once, after a short backoff, for any failure that is not the already-exists race (query-members-by-login-email-is-eventually-consistent above) and not a 4xx -- a 4xx is a validation error that will not fix itself and is reported as member-failed immediately. A caller still seeing member-failed after this retry should treat it as retryable on a LATER run, not terminal: every transient failure observed before this fix succeeded on a subsequent attempt with no other change."
173 }
174 ],
175 "mappingGuidance": [
176 "Split author, customer, contact, and login-member roles before selecting targets.",
177 "Create Member sends no email and does not fire the signup automations trigger (source-confirmed) — silent import is the default; the only member-facing email is the explicit Send Set Password Email call, which is per-member, interactive-mode, owner-approved only."
178 ],
179 "setupRequirements": [
180 "Activation is decided end-to-end: no credential import; post-import the owner creates ONE dashboard automation (label-added trigger → branded email directing members to Log in → Forgot password), then the importer applies the label in API waves for rate control and re-targeting. Enable the automation only after the import window (AUTO-02 pause choreography)."