Documentation
Technical ReferenceREST API

Customer merge

Merge a duplicate customer into the one you keep with the FloPay API, covering the request, preview, idempotency, errors, and the MCP action.

Customer merge

When the same buyer ends up with two customer records at your business, for example after a mistyped email, merge the duplicate into the customer you keep. Its payment history, saved payment methods and subscriptions move with it, and FloPay sends a customer.merged webhook so you can update the customer ids you store.

Both operations are merchant-authenticated and scoped to your merchant account. Client Basic authentication uses the client identifier (client.uuid) as the username and an active API token as the password; OAuth callers use a bearer token. Credentials must remain on a trusted server.

A merge cannot be undone. Preview it first and show its warnings before you merge.

Merge a customer

POST /v1/users/{id}/merge merges the duplicate customer named in the body into the customer in the path.

The customer in the path (id) is the target: the customer that is kept and that every moved record now points at. Both customers must be linked to your merchant account.

curl -X POST https://api.flopay.com/v1/users/$TARGET_USER_ID/merge \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH" \
  -H "Idempotency-Key: $MERGE_IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceUserUuid": "8e5f0c27-4d1b-4a9e-b6c3-1f2a7d9e0b54",
    "keepClientUserId": "customer_123"
  }'

FLOPAY_BASIC_AUTH holds the Base64 encoding of clientUuid:apiToken. MERGE_IDEMPOTENCY_KEY is a new opaque value you generate for this merge, such as a UUID.

FieldTypeRequiredMeaning
sourceUserUuidUUIDYesThe duplicate customer. Its records with you move to the target, and it is removed once nothing else uses it.
keepClientUserIdstringNoWhich of your two customer ids to keep when both customers carry a different one. See Choose the customer id to keep. At most 100 characters.

The Idempotency-Key header is required. A missing or blank Idempotency-Key answers 400. See Idempotency for how a repeated or retried request is answered.

Only merchant owners and admins, or your merchant's API token, can merge or preview a customer. Any other caller gets 403.

A committed merge answers 200. The merge is irreversible: there is no unmerge, and the moved records cannot be split back onto the duplicate.

{
  "operationId": "5d3c8f2a-7b1e-4c9d-a6f0-2e8b4d1c7a93",
  "targetUserId": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
  "status": "completed",
  "movedRows": [
    {
      "table": "flo_card",
      "ids": ["9f1c5f60-6a1e-4a5f-91a1-6b0b0b1f9e21"]
    },
    {
      "table": "subscription",
      "ids": ["d8a2ad28-b98f-4cb6-bf46-f11cc0f5df16"]
    },
    {
      "table": "transaction",
      "ids": ["3c54b6ac-7ad5-4e56-9c2c-5a80c2ef40d0", "7b2e9d41-0c6f-4a3b-8e15-6d4f2a9c0b37"]
    }
  ],
  "providerSync": {
    "status": "succeeded"
  }
}
FieldMeaning
operationIdThe durable merge operation id.
targetUserIdThe customer that was kept, which every moved record now references.
statusAlways completed on a 200.
movedRowsYour records that moved to the target, one entry per table: table and the ids of its moved rows.
providerSyncstatus is succeeded or failed: whether the payment provider's customer details were updated after the merge committed. A failed sync never undoes the merge.

Preview a merge

POST /v1/users/{id}/merge/preview shows what the merge would do with the same body, and writes nothing. It needs no Idempotency-Key, so you can call it as often as you like.

curl -X POST https://api.flopay.com/v1/users/$TARGET_USER_ID/merge/preview \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceUserUuid": "8e5f0c27-4d1b-4a9e-b6c3-1f2a7d9e0b54",
    "keepClientUserId": "customer_123"
  }'
{
  "movedCounts": {
    "flo_card": 1,
    "subscription": 1,
    "transaction": 2
  },
  "conflicts": [],
  "dedupePlan": {
    "removed": [
      {
        "paymentMethodId": "2c7a5e19-8f3d-4b6a-9e02-5d1b7c4f3a86",
        "keptPaymentMethodId": "452d914f-078d-4a68-a9ef-16cac0dd2ed4"
      }
    ]
  },
  "warnings": [
    "The merge is irreversible: there is no unmerge, and the moved rows cannot be split back onto the source user.",
    "The source user will be deleted once its rows have moved to the target user."
  ]
}
FieldMeaning
movedCountsHow many of the duplicate's records with you would move, per table.
conflictsEvery conflict that would refuse the merge right now, each with its code and, where it applies, party (source or target) or tables. Empty when the merge would run. The codes are the merge_in_flight_* codes, merge_unscopable_table, merge_duplicate_payment_method_in_use, merge_client_user_id_choice_required and merge_client_user_id_choice_invalid, described in Errors.
dedupePlanThe duplicate saved cards the merge would remove: in removed, each paymentMethodId to be deleted with the identical keptPaymentMethodId that stays.
warningsConsequences to confirm before merging: that the merge is irreversible, whether the email and names stay unchanged because a customer is shared, and whether the duplicate will be deleted or kept.

Conflicts come back in the 200 body, not as errors, so one preview tells you everything that would stop the merge. Apart from authentication and access (401, 403), the preview answers only 400 for a malformed body or path, 404 merge_user_not_found, and 422 merge_source_is_target.

Preview first, and show its warnings to the person merging before you merge. A preview sends no webhook.

Idempotency

Every merge needs an Idempotency-Key, claimed per merchant:

  • Repeating a completed merge with the same key and the same body returns the original response and does not merge again.
  • Reusing a key for a different request (another duplicate, target or body) answers 409 merge_idempotency_key_reused.
  • While a merge with the key is still running, a repeat answers 409 merge_idempotency_key_in_progress.

Send a new Idempotency-Key for each merge you intend. Retry a failed or timed-out merge with the same key and body: a merge that already committed answers with its original response instead of merging twice. A refused or failed merge frees its key, so you can correct the request and send it again with the same key.

Errors

A 409 means nothing was merged: fix the cause, or retry once the work in progress has finished.

StatusCodeMeaning
400—The body or path is malformed, or the Idempotency-Key header is missing or blank.
401—Credentials are missing or invalid.
403—The caller is not an owner or admin of your merchant account, or lacks the required scope.
404merge_user_not_foundThe customer does not exist or is not linked to you. party is source or target. A missing customer and an unlinked customer answer the same way.
409merge_unscopable_tableThe duplicate has records that cannot be limited to one merchant. tables names them.
409merge_in_flight_payment_method_use_claimA saved payment method of the customer named in party is in use by a payment in progress. Retry once it has finished.
409merge_in_flight_payment_operationA payment for the customer named in party is in progress. Retry once it has finished.
409merge_in_flight_renewal_attemptA subscription renewal for the customer named in party is in progress. Retry once it has finished.
409merge_in_flight_card_deletionA saved card deletion for the customer named in party is in progress. Retry once it has finished.
409merge_in_flight_checkout_attemptA checkout for the customer named in party is in progress. Retry once it has finished.
409merge_in_flight_scheduled_cancellationA subscription cancellation for the customer named in party is in progress. Retry once it has finished.
409merge_duplicate_payment_method_in_useBoth customers hold the same card and both copies are in use.
409merge_operation_open_for_other_targetThe duplicate has an open merge into another customer.
409merge_idempotency_key_reusedThe Idempotency-Key was already used for a different request.
409merge_idempotency_key_in_progressA merge with this Idempotency-Key is still running. Retry once it has finished.
422merge_client_user_id_choice_requiredBoth customers carry a different customer id with you, and the body names none to keep.
422merge_client_user_id_choice_invalidkeepClientUserId is neither of the two existing ids.
422merge_source_is_targetThe duplicate and the target are the same customer.
503merge_dedupe_deletion_incompleteA duplicate saved card could not be removed yet.
503merge_dedupe_provider_default_unverifiableA duplicate saved card could not be checked against the payment provider yet.

On a 503, retry the merge with the same Idempotency-Key and body.

Customers shared with other merchants

A customer can also be a customer of other businesses on FloPay. A merge you run changes only your side of that customer:

  • Only your own records move: your link to the duplicate, and its provider customers, saved payment methods, subscriptions, payments, checkout sessions, invoices and disputes with you.
  • Records the duplicate has with other merchants stay where they are.
  • When either customer is shared with another merchant, the email and names are left unchanged.
  • The duplicate is deleted only when no other merchant still links it and nothing else references it. Otherwise it is kept, and only its link to you is removed.

When both customers hold the same saved card, one copy is kept: the copy a subscription or the customer's default payment method relies on. The other copy is removed, and the preview lists it in dedupePlan.

Choose the customer id to keep

Each customer can carry your own id for it, its client_user_id (clientUserId in API responses and webhooks).

When both customers carry a different client_user_id, keepClientUserId must be one of the two. Leave it out and the merge answers 422 merge_client_user_id_choice_required. Send any other value and it answers 422 merge_client_user_id_choice_invalid. Otherwise keepClientUserId is not needed.

Keep the id your own systems use for the buyer. The id you did not keep is reported as discardedClientUserId in the customer.merged event, next to keptClientUserId, so you can update or retire the record that used it.

Use it from an MCP assistant

MCP assistants run the merge as the User_merge action, with the same body. It is a critical action for owners and admins: the assistant shows you a preview of the merge, and the merge runs only after you confirm it. The REST preview route is not an MCP tool, because the action's own preview is what you confirm.

Webhook

A completed merge sends customer.merged to each affected merchant. See Customer events for who receives it and when, and Customer Payload for every field.

On this page