Create a Payment Link
A Payment Link is a hosted webview URL that lets a Migo merchant collect a payment from an end customer without writing front-end payment code. Your backend creates the link by calling Migo, and you deliver the resulting URL to the customer through any channel you operate (WhatsApp, SMS, email, in-app message, QR code, etc.).
Your only API call is creating the transaction. The customer then completes the entire payment inside Migo's hosted webview β entering the card, choosing installments, passing 3D Secure β and Migo reports the outcome back to your backend through a webhook. You never collect card data and never call a "charge" endpoint yourself.
Payment Link lifecycleβ
ββ your backend βββββββββββββββ
β 1. POST /transactions β ββββββββββββΊ Migo
β (creates the link) β
β β ββββββββββββ 2. { uid, reference, URL }
βββββββββββββββββββββββββββββββ
β
β 3. deliver URL to the customer
β (WhatsApp Β· SMS Β· email Β· QR Β· in-app)
βΌ
ββ customer (hosted webview) ββββββββββββββββββββββββββ
β 4. opens the URL and pays inside Migo's webview: β
β enters card Β· picks installments Β· 3D Secure β
β (no integrator API calls happen here) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β 5. Migo reaches a terminal status
βΌ
ββ your backend βββββββββββββββ
β 6. receives the outcome β ββββββββββββ webhook (Merchant Generic Callback)
β approved / denied / β¦ β { uid, reference, status, β¦ }
βββββββββββββββββββββββββββββββ
Steps 1β3 and 6 are yours; steps 4β5 happen entirely inside Migo. The rest of this page details step 1 (the two ways to create the link) and points you to the webhook for step 6.
Choosing the endpointβ
Migo exposes two endpoints that produce a payment link. They reach the same hosted webview and only differ in how the request is authenticated:
POST /transactions(primary, recommended) β backend-to-backend with a server-side merchant token. Returns the human-friendlyreference.POST /transactions-hook(alternative) β browser-friendly, authenticated with apublicKey+privateKeypair in the body. Noreferenceis returned.
If you control a backend, use POST /transactions. If you have no server at all (static SPA), use POST /transactions-hook. For the full decision tree, the side-by-side comparison, and how to combine both, see Choose your flow.
The result of the payment is later delivered to your backend through the Merchant Generic Callback.
Primary: POST /transactionsβ
This is the recommended endpoint for backend-to-backend integrations.
Endpointβ
POST https://sb-mw.migopayments.com/transactions (Sandbox)
POST https://mw.migopayments.com/transactions (Production)
Content type: application/json. Send the merchant token Migo issued for your application as the value of the Authorization header. The raw token (no scheme prefix) is recommended; a leading Bearer is tolerated and stripped:
Authorization: b6a615d8...0ba65c1 β
recommended (raw token)
Authorization: Bearer b6a615d8... β
accepted (the "Bearer " prefix is stripped)
For the canonical rules on token format and header conventions, see Authentication.
Request bodyβ
| Field | Type | Required | Description |
|---|---|---|---|
amount | integer | yes | Transaction subtotal in the smallest currency unit allowed by your client configuration. Must fall inside the min/max range configured for your client. |
userId | string | yes | Identifier of the end customer in your domain (phone number, email, or your internal id). |
channel | string | yes | Communication channel through which you intend to deliver the payment link to the end customer (for example wa, app, sms, email, web). |
client | string | yes | Slug of your client as registered in Migo configuration. |
ads | array | no | Reserved array, send [] if not used. |
/transactions-hookPOST /transactions and POST /transactions-hook are consumed by the same transaction builder, so the optional fields documented for /transactions-hook below β currency, externalId, customKeys, processorAmount, customerId, subscriptionData, createdBy β are equally accepted by POST /transactions. The cURL and SDK examples that send currency/externalId are therefore valid against this endpoint too.
Allowed values for channel, userId, currencyβ
These three fields are validated against your client configuration. The values you can use depend on what is enabled for your specific client slug:
| Field | Common values | How to discover what your client accepts |
|---|---|---|
channel | wa (WhatsApp), sms, email, web, app | Channels are gated per client. If your call returns ownCode 5000 for channel, the value is not enabled β ask your Migo contact to enable it. |
userId | E.164 phone (+50224865444), email, or your internal id | Format is free-form: Migo stores it verbatim. Pick the format you can correlate with your own user records and stay consistent. For SMS / WhatsApp delivery the field must be a phone Migo can dispatch to. |
currency | ISO 4217 (GTQ, USD, MXN, CRC, HNL, DOP, COP, SVC) | If omitted, Migo uses the first currency configured for your client. If the value isn't in the configured list it is silently coerced to the default β pass it explicitly when you need to be sure. |
There is no public "client config" endpoint today, so the enabled channels, currencies, and processors are not self-service discoverable. Coordinate with your Migo contact to confirm what your client slug accepts, and treat an ownCode 5000/5004 response as a signal that a value is not enabled.
cURL example (Sandbox)β
Replace the placeholder token and client slug with the values issued to you for Sandbox.
curl --location 'https://sb-mw.migopayments.com/transactions' \
--header 'Content-Type: application/json' \
--header 'Authorization: <your-sandbox-token>' \
--data '{
"amount": 1,
"userId": "50224865444",
"channel": "wa",
"client": "<your-client-slug>",
"ads": []
}'
Successful responseβ
{
"uid": "Ypo5z2zTpLqWoe3eVvZMH",
"reference": "CAMPGT020007SB",
"URL": "https://sandbox.migopayments.com/?orderId=Ypo5z2zTpLqWoe3eVvZMH"
}
| Field | Description |
|---|---|
uid | Unique transaction identifier in Migo. Use it to correlate the transaction with the Merchant Generic Callback and with any support enquiry. |
reference | Human-friendly reference that Migo derives from your client configuration. Surfaced in webhook payloads, dashboards, and refund operations. |
URL | Hosted webview URL you deliver to the end customer. The field name is uppercase (URL, not url), exactly as emitted by the handler β keep that casing when you parse the response. |
URLTypeScript / JavaScript snippets that use const { url } = response will silently get undefined. Always destructure as const { URL: paymentLink } = response or read the field with bracket notation response.URL. The same applies to /transactions-hook below.
Alternative: POST /transactions-hookβ
Use this variant in either of the following cases:
- You don't want to use the bearer-token endpoint. You authenticate with a
privateKey+publicKeypair sent in the body, so you don't have to issue and refresh tokens. - You need to call from a browser without CORS issues.
/transactions-hookis configured to accept cross-origin requests, so a front-end can invoke it directly without a server-side proxy.
The hosted webview produced by this variant is identical to the one produced by /transactions. The only behavior difference visible to the merchant is the response shape (no reference is returned).
Endpointβ
POST https://sb-mw.migopayments.com/transactions-hook (Sandbox)
POST https://mw.migopayments.com/transactions-hook (Production)
Content type: application/json. CORS is enabled and authentication is performed inside the handler against the body credentials, so the request does not require an Authorization header.
The OpenAPI spec lists this operation under /mw-default/transactions-hook, but the custom domain at *.migopayments.com strips the /mw-default/ prefix. The public path you call is /transactions-hook (no /mw-default/ segment) β exactly what the cURL example below uses.
Authenticationβ
Send these two fields in the JSON body:
| Field | Description |
|---|---|
privateKey | Private credential of your registered Migo application. |
publicKey | Public credential of your registered Migo application. |
Both credentials are issued to you when your application is registered. If they don't match a registered application, the endpoint responds with HTTP 401 and ownCode 2007.
If you call this endpoint from your backend, treat privateKey as a secret and never embed it in URLs. If you call it from a browser, scope each publicKey / privateKey pair to the specific client and amount range you need so that exposure is bounded.
Request bodyβ
| Field | Type | Required | Description |
|---|---|---|---|
amount | integer | yes | Transaction subtotal in the smallest currency unit allowed by your client configuration. Must fall inside the min/max range configured for your client. |
userId | string | yes | Identifier of the end customer in your domain. |
channel | string | yes | Communication channel through which you intend to deliver the payment link to the end customer. |
client | string | yes | Slug of your client as registered in Migo configuration. |
privateKey | string | yes | Private credential of your registered application. |
publicKey | string | yes | Public credential of your registered application. |
currency | string | no | Currency ISO 4217 code (for example GTQ, USD). If omitted, or if not in the list of currencies allowed for your client, Migo uses the first currency configured for the client. |
processorAmount | object | no | Map of processor name to amount override. Each value must be <= amount. |
customKeys | object | no | Arbitrary merchant-defined data preserved verbatim on the transaction. Available to webview templates and to Generic Callback templates. |
externalId | string | no | Identifier you use in your own system to correlate the Migo transaction with your record. |
createdBy | string | no | Identifier of the operator that created the transaction (used by Migo dashboards). |
customerId | string | no | Internal identifier associating the transaction with a customer record. Stored under additionalData.clientCode. |
subscriptionData | object | no | Subscription metadata appended to additionalData.subscriptionInfo. |
cURL example (Sandbox)β
curl --location 'https://sb-mw.migopayments.com/transactions-hook' \
--header 'Content-Type: application/json' \
--data-raw '{
"privateKey": "<your-sandbox-private-key>",
"publicKey": "<your-sandbox-public-key>",
"amount": 1,
"userId": "50224865444",
"channel": "app",
"client": "<your-client-slug>",
"createdBy": "operator@example.com"
}'
Successful responseβ
{
"uid": "7JV3UKvc2XEretWV2Rb57",
"URL": "https://sandbox.migopayments.com/?orderId=7JV3UKvc2XEretWV2Rb57"
}
/transactions-hook returns { uid, URL } β there is no reference field. If you need the human-friendly reference, use POST /transactions instead, or read the value from the Merchant Generic Callback payload once the transaction reaches a terminal status.
What the customer does nextβ
Once you deliver the URL, everything else happens inside Migo's hosted webview β there is no further API call for you to make. When the customer opens the link, the webview resolves your client configuration and enabled processors automatically and renders the payment UI. Inside it the customer:
- enters their card details in the payment form,
- chooses the number of installments (when the processor supports them),
- passes the 3D Secure challenge if the processor requires it,
- and confirms the payment.
Migo tokenizes the card and charges the transaction internally as part of that flow. You never collect card data, never tokenize, and never call a charge endpoint yourself β those are webview-internal steps. See Hosted webview for how the checkout behaves inside the webview.
Receiving the resultβ
Migo notifies your backend once the transaction reaches a terminal status (approved, denied, refunded, reversed). The Payment Link product delivers this through the Merchant Generic Callback β an outbound POST from Migo to a URL you register with support, carrying { uid, reference, status, amount, currency, ... }.
| Mechanism | Direction | When |
|---|---|---|
| Merchant Generic Callback | Migo β your backend | The transaction reaches a terminal status |
Use the uid you received in step 1 to correlate the callback with the transaction you created. The callback is not signed with HMAC β authenticity is delegated to a static API key/header you register with Migo. See Merchant Generic Callback β Authenticating the request.
Errorsβ
Both endpoints return a Migo ErrorResponseDto. The most actionable field is ownCode, which maps to a Migo error catalog entry.
Payment Link errors use the Middleware ownCode space (low integers like 2002, 2007, 5000, 5004). They are different from the Gateway/CMS error codes (7000β8099) documented in the Error Catalog. The full Payment Link error list is mirrored at the bottom of that page so you can map every code in one place.
| HTTP | ownCode | Cause | What to do |
|---|---|---|---|
| 409 | 5000 | Required fields missing or invalid body | Check that amount, userId, channel, client (and privateKey / publicKey for /transactions-hook) are present with the right types. |
| 401 | 2007 | Invalid body credentials β /transactions-hook only | Verify the privateKey / publicKey pair against your application registry; make sure you are using Sandbox credentials against Sandbox and Production credentials against Production. |
| 401 | (none) | Invalid token on POST /transactions | The token is validated by the API Gateway custom authorizer, not the handler. On failure the gateway returns a generic 401 with body { "message": "Unauthorized" } β there is no Migo ErrorResponseDto and no ownCode. Verify the merchant token and the Sandbox/Production environment match. |
| 409 | 5004 | client slug not found in Migo configuration | Confirm the slug; create or activate the client config in coordination with Migo. |
| 409 | 2003 | amount outside the min/max range configured for your merchant account, or any value in processorAmount exceeds amount | Adjust the amount, or update the per-processor overrides so each is <= amount. |
| 400 | 2006 | End customer is excluded for this client | Excluded users cannot transact for this client. |
| 400 | 2004 | End customer is blocked for this client | Blocked users cannot transact for this client. |
| 400 | 2005 | End customer is suspended for this client | Suspended users cannot transact while the suspension is active. |
| 400 | 2002 | Generic creation error | Retry; if the error persists, contact Migo support with the uid (when available) and the request payload. |
Next stepsβ
- Deliver the
URLto the end customer through your channel of choice. - Implement the Merchant Generic Callback so your backend receives the transaction outcome (
approved/denied/refunded/reversed) once the customer completes the webview. - For a copy-paste browser integration, follow the React Payment Link recipe. For a checklist of every value you need in Sandbox, see the Sandbox cheat sheet.
- For the rest of the payment lifecycle (refunds, captures, processor-specific behavior, 3DS), explore the Migo Middleware API reference and the additional sections of this portal.