Developer guide
API reference
Browse documentation

Verify merchants and send payouts

Register merchants with one or more Pago Móvil payout methods, confirm micro-deposit amounts per method, then pay verified destinations with exact VES amounts and item-level idempotency.

Three golden rules

  • Payout amounts are always explicit tenant strings with exactly two decimals — the gateway never calculates or converts FX for payouts.
  • Idempotency is enforced per externalRef at item level before any bank credit is sent.
  • Every CreditoInmediato AC00 response is reconciled through the same ConsultarOperaciones poller used for débito inmediato.

externalRef rules

You always choose externalRef. The API never generates one. Send your own stable id (user id, settlement id, order number) so you can look records up later.

RuleDetail
RequiredRequired on POST /v1/merchants, POST /v1/payouts, and each POST /v1/payouts/batch item.
Length1–64 characters after trim.
ScopeUnique per tenant. The same string may exist under another tenant.
OwnershipClient-supplied only. Portal Generate is a UI helper for manual testing; production integrations must send their own value in the JSON body.
Replay (same payload)Returns the existing merchant or payout (200) and does not re-hit the bank.
Conflict (different payload)Returns 409 external_ref_conflict — unless the merchant has no payout methods left (e.g. after a BE01 mismatch cleanup or a shell create), in which case POST /v1/merchants with bankCode+phone attaches a new method (200), or POST without them updates the shell identity (200).
RecoveryGET /v1/merchants?externalRef=…, GET /v1/merchants/:id, GET /v1/payouts/by-ref/:externalRef, GET /v1/payouts/:id

Merchant verification

StepEndpoint
1. Register merchantPOST /v1/merchants (optional bankCode + phone creates the first payout method; omit both for a shell merchant)
2. Add another method (optional)POST /v1/merchants/:id/payout-methods
3. Start micro-depositsPOST /v1/merchants/:id/payout-methods/:methodId/verify/start
4. Confirm amountsPOST /v1/merchants/:id/payout-methods/:methodId/verify
5. List methodsGET /v1/merchants/:id/payout-methods
6. Set default methodPOST /v1/merchants/:id/payout-methods/:methodId/default
7. Delete a methodDELETE /v1/merchants/:id/payout-methods/:methodId
8. Read statusGET /v1/merchants/:id or GET /v1/merchants?externalRef=…
9. List payout-readyGET /v1/merchants?status=verified&isActive=true&limit=25
10. Deactivate / reactivatePATCH /v1/merchants/:id `{ "isActive": false|true }`
11. Delete (no payouts)DELETE /v1/merchants/:id — 409 merchant_has_payouts if history exists
Registerbash
curl --request POST "${API_URL}/v1/merchants" \
  --header "content-type: application/json" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{
    "externalRef": "usr_7f3a",
    "name": "Maria Perez",
    "identification": "V12345678",
    "contactEmail": "maria@mail.com",
    "contactPhone": "04141234567",
    "bankCode": "0134",
    "phone": "04145555555"
  }'
Register (no method)bash
curl --request POST "${API_URL}/v1/merchants" \
  --header "content-type: application/json" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{
    "externalRef": "usr_7f3a",
    "name": "Maria Perez",
    "identification": "V12345678",
    "contactEmail": "maria@mail.com",
    "contactPhone": "04141234567"
  }'
# → payoutMethods: [], message: "No payout method exists…"
List / add methodsbash
curl "${API_URL}/v1/merchants/MERCHANT_ID/payout-methods" \
  --header "x-api-key: ${VEXPAY_API_KEY}"

curl --request POST "${API_URL}/v1/merchants/MERCHANT_ID/payout-methods" \
  --header "content-type: application/json" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{
    "bankCode": "0105",
    "phone": "04141234567"
  }'
Verify methodbash
curl --request POST "${API_URL}/v1/merchants/MERCHANT_ID/payout-methods/METHOD_ID/verify/start" \
  --header "x-api-key: ${VEXPAY_API_KEY}"

curl --request POST "${API_URL}/v1/merchants/MERCHANT_ID/payout-methods/METHOD_ID/verify" \
  --header "content-type: application/json" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{ "amount1": "0.37", "amount2": "0.82" }'
Default / deletebash
curl --request POST "${API_URL}/v1/merchants/MERCHANT_ID/payout-methods/METHOD_ID/default" \
  --header "x-api-key: ${VEXPAY_API_KEY}"

curl --request DELETE "${API_URL}/v1/merchants/MERCHANT_ID/payout-methods/METHOD_ID" \
  --header "x-api-key: ${VEXPAY_API_KEY}"
List / statusbash
curl "${API_URL}/v1/merchants?status=verified&isActive=true&limit=25" \
  --header "x-api-key: ${VEXPAY_API_KEY}"

curl "${API_URL}/v1/merchants?externalRef=usr_7f3a" \
  --header "x-api-key: ${VEXPAY_API_KEY}"

curl "${API_URL}/v1/merchants/MERCHANT_ID" \
  --header "x-api-key: ${VEXPAY_API_KEY}"
Deactivatebash
curl --request PATCH "${API_URL}/v1/merchants/MERCHANT_ID" \
  --header "content-type: application/json" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{ "isActive": false }'
Delete merchantbash
curl --request DELETE "${API_URL}/v1/merchants/MERCHANT_ID" \
  --header "x-api-key: ${VEXPAY_API_KEY}"
# 200 → { "deleted": true, "merchantId": "…", "externalRef": "…" }
# 409 → { "error": "merchant_has_payouts", "payoutCount": N }

Delete a merchant

Hard-delete a merchant (and its payout methods) when it has never received a payout. This frees the tenant-scoped externalRef so you can re-register the same seller id. Prefer PATCH isActive:false when payout history exists.

OutcomeDetail
DELETE /v1/merchants/:idTenant-scoped by x-api-key. Path id is the gateway merchant UUID.
200 OK`{ "deleted": true, "merchantId", "externalRef" }` — merchant and payout methods removed; externalRef is free.
404Merchant not found for this tenant.
409 merchant_has_payoutsAt least one payout exists (`payoutCount`). Deactivate with PATCH `{ "isActive": false }` instead.
curlbash
curl --request DELETE "${API_URL}/v1/merchants/00000000-0000-4000-8000-000000000000" \
  --header "x-api-key: ${VEXPAY_API_KEY}"
200 responsejson
{
  "deleted": true,
  "merchantId": "00000000-0000-4000-8000-000000000000",
  "externalRef": "usr_7f3a"
}
409 responsejson
{
  "error": "merchant_has_payouts",
  "payoutCount": 3
}

Find a merchant by externalRef

Within a tenant, each merchant is uniquely identified by the externalRef you sent on register. Prefer looking up by that ref when your system stores the seller/user id, not the gateway merchantId. Auth is the tenant API key (`x-api-key`) — the lookup is always scoped to that tenant.

EndpointReturns
GET /v1/merchants?externalRef={ref}Single merchant detail (same shape as GET /v1/merchants/:id), including payoutMethods[]
GET /v1/merchants/:idSame detail when you already have the gateway UUID
GET /v1/merchantsPaginated list (`items`, `nextCursor`) when externalRef is omitted
curlbash
curl "${API_URL}/v1/merchants?externalRef=usr_7f3a" \
  --header "x-api-key: ${VEXPAY_API_KEY}"
JavaScriptjavascript
const merchant = await fetch(
  `${process.env.API_URL}/v1/merchants?externalRef=${encodeURIComponent('usr_7f3a')}`,
  { headers: { 'x-api-key': process.env.VEXPAY_API_KEY } }
).then((r) => r.json());
// merchant.merchantId, merchant.status, merchant.payoutMethods

Simple and batch payouts

EndpointPurpose
POST /v1/payoutsPay one verified + active merchant (optional payoutMethodId)
POST /v1/payouts/batchPay many merchants; results are per item
GET /v1/payoutsList payouts (optional status filter)
GET /v1/payouts/:idRecover by payout id
GET /v1/payouts/by-ref/:externalRefRecover by externalRef
Simple payoutbash
curl --request POST "${API_URL}/v1/payouts" \
  --header "content-type: application/json" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{
    "merchantId": "00000000-0000-4000-8000-000000000000",
    "payoutMethodId": "00000000-0000-4000-8000-000000000001",
    "monto": "1523.40",
    "concepto": "Liquidacion semana 30",
    "externalRef": "po_9c21"
  }'

POST /v1/payouts/batch pays many verified merchants in one request. Each item is its own payout (own externalRef, status, and webhooks). Failures are per item — they do not roll back siblings.

Batch payoutbash
curl --request POST "${API_URL}/v1/payouts/batch" \
  --header "content-type: application/json" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{
    "externalRef": "corte_semana_30",
    "items": [
      {
        "merchantId": "00000000-0000-4000-8000-000000000000",
        "payoutMethodId": "00000000-0000-4000-8000-000000000001",
        "monto": "1523.40",
        "concepto": "Liquidacion semana 30",
        "externalRef": "po_9c21_seller"
      },
      {
        "merchantId": "00000000-0000-4000-8000-000000000002",
        "monto": "210.00",
        "concepto": "Liquidacion semana 30",
        "externalRef": "po_9c21_partner"
      }
    ]
  }'
201 responsejson
{
  "batchId": "00000000-0000-4000-8000-000000000010",
  "items": [
    {
      "payoutId": "00000000-0000-4000-8000-000000000011",
      "status": "completed",
      "reference": "12345678",
      "failureCode": null,
      "externalRef": "po_9c21_seller",
      "merchantId": "00000000-0000-4000-8000-000000000000",
      "payoutMethodId": "00000000-0000-4000-8000-000000000001",
      "merchantName": "Maria Perez",
      "monto": "1523.40",
      "concepto": "Liquidacion semana 30",
      "bankCode": "0134",
      "destination": "…5555",
      "operationId": null,
      "createdAt": "2026-07-28T16:00:00.000Z",
      "settledAt": "2026-07-28T16:00:01.000Z"
    },
    {
      "payoutId": "00000000-0000-4000-8000-000000000012",
      "status": "pending",
      "reference": null,
      "failureCode": null,
      "externalRef": "po_9c21_partner",
      "merchantId": "00000000-0000-4000-8000-000000000002",
      "payoutMethodId": "00000000-0000-4000-8000-000000000003",
      "merchantName": "Socio CA",
      "monto": "210.00",
      "concepto": "Liquidacion semana 30",
      "bankCode": "0102",
      "destination": "…1234",
      "operationId": "AC00-OP-EXAMPLE",
      "createdAt": "2026-07-28T16:00:00.000Z",
      "settledAt": null
    }
  ]
}
List / statusbash
curl "${API_URL}/v1/payouts?status=pending&limit=25" \
  --header "x-api-key: ${VEXPAY_API_KEY}"

curl "${API_URL}/v1/payouts/PAYOUT_ID" \
  --header "x-api-key: ${VEXPAY_API_KEY}"

Webhook events

  • merchant.verified / merchant.rejected
  • merchant.deactivated / merchant.reactivated
  • payout.completed / payout.failed
  • Envelope remains { event, data, timestamp } with X-Webhook-Signature over the exact raw body.
merchant.rejected (deleted)json
{
  "event": "merchant.rejected",
  "data": {
    "merchantId": "292d478b-a1fb-4d27-ab5c-8701ed14da88",
    "externalRef": "47b53d97-489f-4f8e-8843-6c24045e168d",
    "payoutMethodId": "0064456b-7a6a-4b7e-b0b5-d9b27b0dfa5c",
    "failureCode": "BE01",
    "reason": "Datos del cliente no corresponden a la cuenta",
    "action": "payout_method_deleted"
  },
  "timestamp": "2026-07-23T18:12:05.000Z"
}