Skip to main content

Troubleshooting: merge refused with WEAK_IDENTITY_MATCH_REQUIRES_CONFIRMATION

A 422 with code WEAK_IDENTITY_MATCH_REQUIRES_CONFIRMATION means POST /api/v1/conversations/:id/merge looked at the rows you asked it to fold and found a phone number or an email that matches — but also found contact_id values that disagree with each other. The merge is refused rather than risk folding two different customers into one thread. This is a confirmation gate, not a hard block: re-submit the request with confirm_weak_identity=true to proceed.

What a merge does

POST /api/v1/conversations/:id/merge folds one or more sibling conversation rows into the target (primary) conversation:
  • Each source row is archived (status archived, not deleted) with metadata.merged_into pointing at the primary, so deep links and audit trails still resolve.
  • The primary’s channels[] is unioned with every source’s channels, so the existing thread query loads messages from all of them.
  • A pre-merge snapshot is stamped on each source, which powers POST /api/v1/conversations/:id/unmerge — you can reverse a merge within 30 minutes (see Undo a recent conversation merge).

Identity signals, from strongest to weakest

The merge gate classifies each primary/source pair by which identifier lines up: The refusal exists for the collisions a phone-or-email-only match cannot rule out: a shared family email, a shared office or call-center number, a recycled phone number now registered to a new person. In all of those, the weak identifier overlaps while the rows resolve to different contacts — folding them merges two customers’ histories into one thread. When it fires, the response tells you which rows and which identifier collided in error.details:
match_type is phone or email — check it first. A VALIDATION_ERROR instead of this code means the rows share nothing at all; no confirmation flag will help there.

Confirm and retry

Once you have verified the two conversations really do belong to the same customer, send the same request with the confirmation flag:
Before confirming, check the two conversations in the inbox. If the phone or email is genuinely shared (a household, an office line), decide which contact is the right one first: if you intended these conversations to live under one contact record, resolve the duplicate contacts in Audience → Identity resolution and re-run the merge — a strong contact_id match then passes without the flag. If you confirmed a merge and it folded rows it should not have, POST /api/v1/conversations/:id/unmerge restores every source from its pre-merge snapshot within 30 minutes. After the window, support can do a manual revert.

Where matching rules live

Conversation merging reuses the identifier layers from contact identity resolution — normalized email, E.164 phone, external_id. The full guide covers merge candidates, match reviews, the rule simulator, and survivorship policy: Identity resolution. Conversation rows inherit the contacts those rules produce, so resolving duplicate contacts upstream makes conversation merges pass on the strong tier.

Send support

When you open a ticket for a merge question, include:
  • The request_id from meta.request_id on the failed (or confirmed) response
  • Both conversation ids — the primary from the path and every id in source_conversation_ids
  • The identifier pair that overlapped: match_type from error.details plus the phone or email value, and the two contact_id values from details
With those, support can replay the gate decision and confirm which tier fired without asking you to reproduce it.
A weak-identity refusal is the merge doing its job. Confirm only after you have checked the two threads belong to one customer — unmerge past the 30-minute window needs a manual support revert.