Embedded integration examples
BNPL — Embedded checkout (URL for iframe or modal)
Overview
Create Payment with BNPL (POST /v1/payment/orders) can run as an embedded checkout integration: the API returns a return_url pointing to the Koin hosted checkout. The merchant site uses that URL to load the flow in an iframe or modal, without sending the user to a full-page redirect off the merchant domain (except where business rules or the checkout’s own navigation apply).
High-level flow:
- The merchant backend calls Create Payment with the BNPL method and the embedded-flow parameters described below.
- The response includes
return_url: the checkout URL to embed. - The front end opens that URL in an iframe or modal according to your UX.
For the full order body (transaction, payer, items, shipping, etc.), follow the Payment BNPL schema in the Payments API. This page focuses on what is specific to embedded checkout and the response shape that includes the URL.
Root-level body fields (embedded checkout)
Send these fields in the root JSON object of the request, together with the rest of the BNPL payload:
| Field | Type | Expected value |
|---|---|---|
mobile | boolean | true — flow suited to the embedded experience (checkout inside your site). |
partner | object | Contains marketing_link. |
partner.marketing_link | boolean | false — disables marketing links in the embedded flow. |
Example (snippet):
{
"mobile": true,
"partner": {
"marketing_link": false
}
}Response: return_url
return_urlOn success, the API may return an object that includes the transaction status, a summary of the order, and the return_url used to embed the checkout.
Illustrative example:
{
"status": {
"type": "Opened",
"date": "2021-09-03T15:27:28.000Z"
},
"transaction": {
"reference_id": "REF0000001",
"business_id": "BIZ0000001",
"account": "SBX000",
"amount": {
"currency_code": "BRL",
"value": 1500.56
}
},
"country_code": "BR",
"return_url": "https://payments.koin.com.br/checkout/2609bd4a-60ba-8fb6-ac9f-0b28f67f679e"
}return_url: hosted checkout URL — use as the iframesrcor as the URL opened in a modal (e.g. webview or embedded window), depending on your implementation.status: typical initial transaction state (e.g.Opened) and reference timestamp.transaction: echo of identifiers and charge amount.
Additional fields may appear depending on the product (e.g. order_id, installment_option in transparent checkout). Refer to the full BNPL API contract.
Transaction lifecycle: callback via notification_url
notification_urlAll tracking of BNPL order progress after creation must be handled through HTTP notifications (callback / webhook) that Koin sends to the URLs you provide in the notification_url field in the Create Payment body.
- On create, send
notification_urlas an array of strings (one or more HTTPS URLs on your backend). - Whenever the order status changes, Koin may POST the notification payload to those URLs — as described in Introduction — BNPL (“When the order status changes a webhook notification will be sent.”).
- The iframe/modal flow serves the buyer experience; do not rely on the embedded UI alone as the only source of truth to fulfill the order or update your ERP: treat the callback as the official channel for status updates.
Summary: configure
notification_urlcorrectly, expose an idempotent endpoint (able to receive the same transition more than once), and update your systems from thestatusin the notification body.
Securing your webhook endpoint
Allowlist Koin IPs. For security, accept only HTTP requests to your callback URL that originate from Koin’s outbound addresses:
| IP address |
|---|
3.210.58.254 |
3.216.200.86 |
44.199.169.87 |
Configure your firewall, API gateway, or application so that webhook traffic is rejected unless the client IP (as observed after any trusted proxy/load balancer — use the connecting peer or the value your platform documents for “true client IP”) matches one of these addresses. Do not rely on easy-to-spoof headers alone unless your edge infrastructure sets them from the TCP connection.
Optional double-check. You can further harden the flow by reconciling each notification with Koin’s lookup API: after receiving the webhook, call Get Payments by Transaction ID using the payment/transaction identifier from your integration (transaction_id query parameter on GET /v1/payment/orders/lookup) and compare the returned order state with the payload you received. Use the same authentication scheme as for other Payments API calls (sandbox base URL: https://api-sandbox.koin.com.br).
Example (adjust YOUR_ACCESS_TOKEN and REF0000001):
curl -s -G 'https://api-sandbox.koin.com.br/v1/payment/orders/lookup' \
--data-urlencode 'transaction_id=REF0000001' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'The table below summarizes the states described in the BNPL documentation (see the official page for full detail and test scenarios by amount range):
Status (type) | reason (when applicable) | Description (summary) |
|---|---|---|
Opened | — | Order created. |
Waiting | FirstPayment | Awaiting the first payment. |
Waiting | EmailValidation | Awaiting email validation. |
Waiting | WhatsAppValidation | Awaiting WhatsApp validation. |
Pending | ProviderReview | Under review with the provider. |
Collected | — | Order paid / amount settled per product rules. |
Cancelled | — | Order canceled or expired. |
Failed | — | Failure in creation or flow. |
Failed | Rejected | Credit denied. |
The JSON below are illustrative examples of what your callback endpoint might receive: structure aligned with the BNPL order model (status with type, reason when present, date, and transaction). The exact contract (extra fields, headers, or signing) may appear in the API notifications/webhooks reference — validate against the current documentation and with the Koin team.
Example: order created (Opened)
Opened){
"status": {
"type": "Opened",
"date": "2021-09-03T15:27:28.000Z"
},
"transaction": {
"reference_id": "REF0000001",
"business_id": "BIZ0000001",
"account": "SBX000",
"amount": {
"currency_code": "BRL",
"value": 1500.56
}
},
"country_code": "BR",
"order_id": "9dd3f765-a51a-49d8-b6af-a51d5a0b3f7f"
}Example: under review (Pending + ProviderReview)
Pending + ProviderReview){
"status": {
"type": "Pending",
"reason": "ProviderReview",
"date": "2021-09-03T15:30:00.000Z"
},
"transaction": {
"reference_id": "REF0000001",
"business_id": "BIZ0000001",
"account": "SBX000",
"amount": {
"currency_code": "BRL",
"value": 1500.56
}
},
"country_code": "BR",
"order_id": "9dd3f765-a51a-49d8-b6af-a51d5a0b3f7f"
}Example: awaiting first payment (Waiting + FirstPayment)
Waiting + FirstPayment){
"status": {
"type": "Waiting",
"reason": "FirstPayment",
"date": "2021-09-03T15:35:00.000Z"
},
"transaction": {
"reference_id": "REF0000001",
"business_id": "BIZ0000001",
"account": "SBX000",
"amount": {
"currency_code": "BRL",
"value": 1500.56
}
},
"country_code": "BR",
"order_id": "9dd3f765-a51a-49d8-b6af-a51d5a0b3f7f"
}Example: awaiting email validation (Waiting + EmailValidation)
Waiting + EmailValidation){
"status": {
"type": "Waiting",
"reason": "EmailValidation",
"date": "2021-09-03T15:40:00.000Z"
},
"transaction": {
"reference_id": "REF0000001",
"business_id": "BIZ0000001",
"account": "SBX000",
"amount": {
"currency_code": "BRL",
"value": 12000.00
}
},
"country_code": "BR",
"order_id": "9dd3f765-a51a-49d8-b6af-a51d5a0b3f7f"
}Example: awaiting WhatsApp validation (Waiting + WhatsAppValidation)
Waiting + WhatsAppValidation){
"status": {
"type": "Waiting",
"reason": "WhatsAppValidation",
"date": "2021-09-03T15:42:00.000Z"
},
"transaction": {
"reference_id": "REF0000001",
"business_id": "BIZ0000001",
"account": "SBX000",
"amount": {
"currency_code": "BRL",
"value": 12000.00
}
},
"country_code": "BR",
"order_id": "9dd3f765-a51a-49d8-b6af-a51d5a0b3f7f"
}Example: paid (Collected)
Collected){
"status": {
"type": "Collected",
"date": "2021-09-03T16:00:00.000Z"
},
"transaction": {
"reference_id": "REF0000001",
"business_id": "BIZ0000001",
"account": "SBX000",
"amount": {
"currency_code": "BRL",
"value": 1500.56
}
},
"country_code": "BR",
"order_id": "9dd3f765-a51a-49d8-b6af-a51d5a0b3f7f"
}Example: credit denied (Failed + Rejected)
Failed + Rejected){
"status": {
"type": "Failed",
"reason": "Rejected",
"date": "2021-09-03T16:05:00.000Z"
},
"transaction": {
"reference_id": "REF0000001",
"business_id": "BIZ0000001",
"account": "SBX000",
"amount": {
"currency_code": "BRL",
"value": 75000.00
}
},
"country_code": "BR",
"order_id": "9dd3f765-a51a-49d8-b6af-a51d5a0b3f7f"
}Example: order canceled or expired (Cancelled)
Cancelled){
"status": {
"type": "Cancelled",
"date": "2021-09-04T10:00:00.000Z"
},
"transaction": {
"reference_id": "REF0000001",
"business_id": "BIZ0000001",
"account": "SBX000",
"amount": {
"currency_code": "BRL",
"value": 1500.56
}
},
"country_code": "BR",
"order_id": "9dd3f765-a51a-49d8-b6af-a51d5a0b3f7f"
}To force scenarios in the test environment (amount ranges that drive intermediate and final statuses), use the Testing environment table on the same Introduction — BNPL page.
Authentication and environment
- Sandbox:
https://api-sandbox.koin.com.br - Production: use the base URL provided by Koin for your environment.
Include the headers and authentication mechanism defined in your contract (e.g. Bearer token or the scheme agreed with Koin).
Example: cURL request
The example below sends mobile and partner at the root and a minimal valid BNPL body (adjust credentials, notification URLs, and real data). Replace YOUR_ACCESS_TOKEN with your API token.
curl -X POST 'https://api-sandbox.koin.com.br/v1/payment/orders' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--data-binary @- <<'EOF'
{
"mobile": true,
"partner": {
"marketing_link": false
},
"transaction": {
"reference_id": "REF0000001",
"business_id": "BIZ0000001",
"account": "SBX000",
"amount": {
"currency_code": "BRL",
"value": 1500.56,
"breakdown": {
"items": { "currency_code": "BRL", "value": 1000 },
"shipping": { "currency_code": "BRL", "value": 500.56 },
"taxes": { "currency_code": "BRL", "value": 0 }
}
}
},
"country_code": "BR",
"payment_method": {
"code": "BNPL",
"expiration_date": "2021-12-31T23:59:59.000Z"
},
"payer": {
"document": { "type": "cpf", "number": "54672396002" },
"email": "[email protected]",
"phone": {
"country_code": "+55",
"area": "11",
"number": "984565666",
"type": "Mobile"
},
"address": {
"country_code": "BR",
"state": "SP",
"city_name": "SAO PAULO",
"street": "RUA ABRAO WAITMAN",
"number": "500",
"zip_code": "04788240",
"district": "INTERLAGOS"
}
},
"device_fingerprint": "3f139f92d30d54f1aaf519b3a39a20814",
"notification_url": ["https://www.yourdomain.com/webhooks/payments"],
"shipping": {
"delivery_date": "2021-11-01",
"address": {
"country_code": "BR",
"state": "SP",
"city_name": "SAO PAULO",
"street": "RUA ABRAO WAITMAN",
"number": "500",
"zip_code": "04788240",
"district": "INTERLAGOS"
}
},
"items": [
{
"type": "Generic",
"id": "13238487",
"name": "Sample product",
"quantity": 1,
"category": { "id": "111", "name": "Category" },
"price": { "currency_code": "BRL", "value": 1000.00 }
}
],
"ip_address": "192.0.2.146",
"verified_id": true
}
EOFThe expected response includes return_url for use in the iframe or modal, as in the Response: return_url section.
Front-end best practices
- Iframe: set
sandboxand security policies according to your store policy and web security guidance. - Modal: provide adequate size and clear dismissal; handle user outcome (success/cancel) via payment notifications and, where applicable, product-configured URLs.
- HTTPS: always use HTTPS in production.
Updated 4 days ago
