Implementing Relinking and Detailed Error Messaging
Purpose
Credentials expire, permissions get revoked, and admins change — relinking lets end users re-authenticate without contacting support. Detailed error messaging (rather than "something went wrong") tells users exactly what failed and who needs to fix it.
Prerequisites
post-connection-build-settings-pagecomplete/api/merge/relink-integrationendpoint working
Before Proceeding
Tell the user: "Implementing relinking requires storing error state on the linked_accounts table (an error_category column and optionally error_detail). I'll generate a migration for this. Ready to proceed?"
Wait for confirmation before continuing.
Detecting that a relink is needed
There is no relink_needed webhook. The dashboard's event list shows LinkedAccount.status_changed, but it has no emitter and never fires — do not build detection on it. Three signals that do work:
Issue.newwebhook — fires when Merge opens an issue on the account (expired credentials, missing permissions, provider outage). Pair withIssue.resolvedto clear your banner.- Poll
GET /issues?linked_account_id={id}&status=ONGOING— the reliable backstop, and the only way to get the error detail you need for the message copy in Part 2. statusonGET /account-details—RELINK_NEEDEDis the account-level signal, but it lags the underlying issue.
Use 1 for latency and 2 for correctness. An account can be broken without a webhook ever arriving, so the poll is not optional.
Part 1: Relinking Flow
Prompt the codebase to implement relinking with multiple entry points and clean state management:
Implement relinking with the following requirements:
Entry points — expose the reconnect action from at least two places:
- Settings page: always-visible "Reconnect" button
- Error banner CTA: "Reconnect" link surfaced when status is not active
- Email notification link (deep-link into the settings page reconnect flow)
Flow:
- Call
POST /api/merge/relink-integrationto get a new link token- Open Merge Link modal using that token
- On
onSuccesscallback: refresh integration status from DB and update UISuccess state: Set
status = "active"inlinked_accounts, clear any stored error state (error category, error detail fields).Failure handling: Show the specific error returned; do not wipe or overwrite the existing
linked_accountsrecord.
Two relink paths, different outcomes
There are two reasons a Linked Account ends up needing reconnection. They look identical in your UI but produce different outcomes — make sure your reconnect copy doesn't promise restoration in the second case:
| Trigger | What's still on Merge's side | What relink does | Result |
|---|---|---|---|
Credentials revoked at source (token expired, user deauthorized in Jira/Salesforce/etc.). Linked Account status = "relink_needed" |
Linked Account record + sync history fully intact | Updates credentials in place using the same end_user_origin_id |
Same merge_account_id, same account_token (may stay valid), sync history preserved |
| Linked Account deleted from the Merge dashboard | Nothing — record gone | Degrades to a fresh connect under the same end_user_origin_id |
New merge_account_id, new account_token, sync history starts over |
⚠️ "Delete + Reconnect" is not equivalent to "Reconnect." If your UI offers a Delete button alongside Reconnect, make sure users understand that Delete is not a "force refresh" — it permanently severs the Linked Account, including any references to the old merge_account_id (e.g. webhook payloads stored before the delete will become orphans). Reserve Delete for genuine "remove this integration" flows.
Part 2: Error Messaging
Prompt the codebase to integrate with Merge's Issues API and surface human-readable messages:
Use
GET https://api.merge.dev/api/{category}/v1/issues?linked_account_id={linked_account_id}(Merge Issues API) to fetch structured error information for the linked account.Issues API response (each issue):
Field Type Notes idstring (UUID) Issue ID statusstring ONGOING,RESOLVEDerror_descriptionstring Human-readable error summary, e.g. "Missing Permissions"error_detailsarray of strings Specific details, e.g. ["Missing employee permissions.", "Missing time off permissions."]— plural, and an array, so join or list them rather than rendering the raw valuefirst_incident_timedatetime or null When the issue first appeared last_incident_timedatetime or null Most recent occurrence is_mutedboolean Whether someone muted this issue in the dashboard end_userobject The end user the issue belongs to ⚠️ The field is
error_details(array), noterror_detail(string), and there is nolinked_accountfield on an Issue. Scope the request instead:?linked_account_id={id}or?account_token={token}. The response is paginated like every list endpoint — readnextand follow the cursor.⚠️ Muted issues are excluded by default.
GET /issuesomits anything muted in the dashboard unless you passinclude_muted=true. If your banner disappears while the account is still broken, someone muted the issue. Decide deliberately whether your UI should see muted issues — for a customer-facing health banner, honoring the mute is usually right; for an internal ops view, passinclude_muted=true.Other useful filters:
status=ONGOING,first_incident_time_after,last_incident_time_after,integration_name,end_user_organization_name.For each issue, surface a message that answers: what is broken, who needs to fix it, and what action to take.
Map error categories to the following message patterns:
Category Message Actor Auth failure / expired credentials "Your [Integration] credentials have expired. Click Reconnect to re-authenticate." End user Missing permissions "Your [Integration] account is missing required permissions. Have your [Integration] admin grant [specific permission]." Admin Billing / plan restriction "Access to [Integration] is blocked due to a plan restriction. Contact [Integration] support or your account admin." Admin or support Integration outage "[Integration] is currently experiencing an outage. No action needed — we'll retry automatically." None Replace
[Integration]with the integration name from the linked account record. Replace[specific permission]with the entries inerror_detailswhen the array is non-empty.Store the latest error category on the
linked_accountsrecord so the UI can render the correct banner without re-fetching issues on every page load.
"Live" Checklist
- Relinking accessible from at least 2 entry points (settings page, error banner)
- Reconnect flow works end-to-end without contacting support
- Error messages state the specific error category (not generic "something went wrong")
- Each error message clarifies who must act (end user / admin / support)
- Successful relink resets
status = "active"inlinked_accountsand clears error state - Relink failure does not delete or overwrite the existing account record