Account Merge Procedure
Status: Active
Last updated: 2026-06-21 UTC
Owner: Customer Support
Console path: /console/merges
Related epic: #3245
Related card: #3252 (this runbook)
Design doc: docs/architecture/account-merge-2026-06-05.md
Overview
Account merging is a CS-initiated operation that consolidates two customer accounts into one. The feature is gated by FLAG_ACCOUNT_MERGE. Do not attempt this procedure if that flag is OFF on the target environment — the console paths will return 404.
Key invariants:
- CS initiates and selects the primary account. Customers cannot request a primary swap.
- Both email addresses must independently verify before the merge executes. There is no CS bypass.
- All merge actions are audit-logged and trigger a FreeScout ticket auto-comment.
1. Prerequisites
Before initiating a merge, confirm each of the following. If any check fails, do not proceed — resolve the blocker first and note it in the FreeScout ticket.
1.1 Identity confirmation
- [ ] Confirm that both email addresses belong to the same person. Acceptable evidence: the customer provided both addresses in a single support request, or two separate tickets reference each other with corroborating context.
- [ ] If there is any doubt about ownership, request the customer to confirm from both email addresses before proceeding. Do not initiate on verbal or partial evidence.
1.2 Open DSR check
- [ ] Check the audit log and DSR queue (
/console/dsr) for any pending data subject request (deletion, access, portability) on either account. A merge while a DSR is in-flight can complicate fulfillment. - [ ] If an open DSR exists on either account, hold the merge and escalate to
ops@raxx.app. Do not close the DSR to unblock the merge.
1.3 Open FreeScout ticket
- [ ] Open or locate the FreeScout ticket for the customer's merge request.
- [ ] The FreeScout ticket number is required for the Console merge form. When the merge is created in Console, a FreeScout auto-comment will appear on that ticket with the merge ID and status.
- [ ] If no ticket exists, create one in FreeScout under the customer's primary email before proceeding.
2. Initiating a merge
- Navigate to
/console/mergesin the Console. - Click New merge.
- Enter the primary email address (the surviving account). CS selects the primary — the customer cannot request a change after this step.
- Enter the secondary email address (the account to be deactivated).
- Enter the FreeScout ticket number for this request.
- Review the account summary displayed: subscription status, passkey count, strategy count, open positions flag for each account.
- Click Initiate merge.
Console sends a verification email to both addresses. The merge status transitions to initiated.
3. Monitoring verification
After initiation, the merge detail view at /console/merges/<merge_id> shows real-time verification status.
| Field | Meaning |
|---|---|
primary_verified_at |
Timestamp when the primary account holder clicked verify. Null until confirmed. |
secondary_verified_at |
Timestamp when the secondary account holder clicked verify. Null until confirmed. |
status |
initiated → in_progress → completed (or cancelled / reversal_pending) |
expires_at |
Verification window deadline. After this, codes expire and must be resent. |
The merge engine advances to in_progress automatically once both *_verified_at fields are set. It advances to completed once the data migration completes. No CS action is needed in the intermediate states unless an edge case arises (see Section 4).
4. Edge cases
4.1 One email fails delivery (bounce or no response)
Symptom: Customer reports they did not receive the verification email. Console detail view shows primary_verified_at or secondary_verified_at is null after expected delivery time.
Steps:
- Ask the customer to check spam/junk folders and any email filtering rules.
- If no message was received, click Resend verification next to the affected address in the Console detail view. The console resends and resets the expiry window.
- If resend bounces (Postmark bounce event appears in Console), confirm the email address with the customer. Possible causes: typo in the address provided, inbox over quota, domain rejecting Postmark.
- If the customer cannot access the email address at all, do not proceed. The account cannot be verified without access to that address. Close the merge (
Cancelin Console), note the reason in the FreeScout ticket, and advise the customer to regain email access before re-requesting.
4.2 One account has an active paid subscription (billing collision — D4)
Symptom: Console detail view shows a non-free subscription status on the secondary account.
This is expected and handled during the merge flow. The customer is prompted via email to choose:
- (A) Apply the remaining subscription credit from the secondary account as a balance credit on the primary account.
- (B) Refund the remaining credit to the payment method on file for the secondary account.
Steps:
- Monitor the merge detail view for
billing_collision_resolved_at. The merge will not advance toin_progressuntil the customer has made a billing choice. - If the customer has not responded to the billing choice within 24 hours, resend the billing choice email from the Console detail view (Resend billing prompt).
- If the customer requests a custom arrangement (e.g., partial credit, delayed refund), do not proceed unilaterally — escalate to the billing team via a FreeScout internal note before the merge completes.
- Founders pricing note: If either account holds Founders pricing, Founders pricing is preserved on the merged account regardless of which account was primary.
4.3 One account has pending paper positions
Symptom: Console detail view shows pending_paper_positions: true on one account.
Action: No blocking action required. Paper positions from the secondary account are summed with those of the primary account at merge time. The merge proceeds normally. Notify the customer in the FreeScout ticket that paper positions from both accounts were combined.
4.4 Verification code expired
Symptom: Console detail view shows expires_at in the past and one or both *_verified_at fields are still null. Merge status is initiated but stalled.
Steps:
- Click Resend verification in the Console detail view for the unverified address. This generates a new code and resets the expiry window.
- Notify the customer in the FreeScout ticket that a new verification message was sent.
- If codes expire repeatedly without the customer verifying, ask the customer to confirm they are using the correct email address and that they can access it.
4.5 Customer requests a primary swap before first verification
Symptom: After initiation, the customer contacts support saying they want the other account to be primary.
Action: The primary designation is CS-only and cannot be changed after initiation. The customer cannot request a swap.
Steps:
- Cancel the current merge in the Console (
Cancelbutton on the detail view). Note the reason in the FreeScout ticket. - Initiate a new merge with the desired primary and secondary reversed.
- Verification emails are resent to both addresses for the new merge.
Do not attempt to alter primary_user_id directly. This is not available in the Console and would violate a mandatory invariant (M3) in the merge engine.
5. Reversal procedure
Reversals are available within 14 calendar days of the completed_at timestamp on the merge record.
Prerequisites:
- Reversal must be approved by an operator-level Console user who is different from the user who initiated the original merge (four-eyes rule).
- Both original email addresses are notified and have a 24-hour hold window before the reversal executes.
Steps:
- Navigate to
/console/merges/<merge_id>. - Confirm
completed_atis within the last 14 days. If outside the window, reversals are not available — advise the customer accordingly and close the FreeScout ticket. - Click Request reversal. Enter the reason.
- The reversal request enters
reversal_pendingstatus. An operator-level user (different from you) must approve in the Console approval queue (/console/merges/reversals). - Upon approval, both original email addresses receive a notification. A 24-hour hold begins.
- After the 24-hour hold, the operator who approved must click Confirm reversal to execute. Alternatively, click Abort reversal if the customer changes their mind during the hold.
- When reversal executes: the primary account reverts to its pre-merge state; the secondary account is reactivated; merged data originating from the secondary account is separated back out per the merge record.
- Note in the FreeScout ticket: reversal completed, both accounts restored, merge ID and reversal timestamp.
Limitations: Reversal availability may be restricted if data from the secondary account has been substantially modified after the merge (e.g., new trades entered under the merged account that cannot be cleanly attributed). Console will surface a warning in this case — escalate to ops@raxx.app before proceeding.
6. Post-merge checklist
After a merge completes (status = completed):
- [ ] FreeScout auto-comment received. Open the FreeScout ticket and confirm the auto-comment appeared with the merge ID and
completedstatus. If the auto-comment is missing, the FreeScout webhook may have failed — check/console/webhooksfor the delivery log and manually note the merge completion in the ticket. - [ ] Confirm merged data visible in primary account. In the Console account view for the primary user, verify that strategy count and trading history count reflect the expected combined totals.
- [ ] Confirm secondary account deactivated. In the Console account view for the secondary user, status should read
deactivated. The user should not be able to log in. - [ ] Reply to FreeScout ticket. Send the customer a confirmation reply: merge completed, what was merged, and a reminder of the 14-day reversal window. Use the standard merge-confirmation FreeScout template if available.
- [ ] Close the FreeScout ticket once the customer acknowledges or after 48 hours with no reply.
Related references
- Customer help article:
docs/customer/account-merging.md(published atdocs.raxx.app/account-merging) - Privacy disclosure:
frontend/getraxx-landing/src/pages/legal/PrivacyPolicy.jsx§7b "Account Merging" - Design doc:
docs/architecture/account-merge-2026-06-05.md - ADR:
docs/architecture/adr/0113-account-merge-architecture.md - Epic: #3245
- This runbook: #3252