Inline.js
Embed Chapa checkout directly into your website for a smoother, in-page payment experience.
Chapa Inline.js allows you to embed Chapa checkout directly into your website or application, providing a smoother, in-page payment experience instead of a full-page redirect.
Inline.js is ideal when you want:
- A modern, in-page checkout experience
- Faster user flow with fewer context switches
- Better conversion compared to full-page redirects
- Minimal frontend effort while Chapa handles payment processing securely
Important: Your secret API key must always remain on your server. Inline integrations should use a public key (if supported by your setup), or a server-generated checkout session returned from your backend.
How Inline Checkout Works
- Customer clicks Pay Now
- Your frontend triggers Inline.js
- Inline.js opens the checkout UI
- Customer completes payment
- Your system confirms payment via:
- Server-side verification
- Webhooks (recommended)
Frontend callbacks are UX signals only, not proof of payment.
Recommended Flow (Safe & Consistent)
Even when using Inline.js, the safest and most consistent flow is:
- Frontend collects customer and order info
- Backend initializes payment (
POST /v2/payments/hosted) - Frontend opens Inline checkout using returned session/URL
- Backend verifies payment (
GET /v2/payments/{reference}/verify) - Webhooks confirm final state
Step 1: Add Inline.js to Your Page
Include the Inline.js script in your HTML:
<script src="https://checkout.chapa.co/checkout.js"></script>If your production Inline.js URL differs, replace it with the official script provided by Chapa.
Step 2: Create a Pay Button
<button id="pay-btn">Pay Now</button>Step 3: Initialize Inline Checkout
Inline.js should never call Chapa directly with secret keys. Instead, your frontend requests a payment session from your backend.
Example Frontend Code:
<script>
document.getElementById("pay-btn").addEventListener("click", async () => {
const res = await fetch("/initiate-payment", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
amount: 25000,
currency: "ETB",
merchant_reference: "ORDER_" + Date.now(),
customer: {
first_name: "John",
last_name: "Doe",
email: "john.doe@example.com",
phone_number: "251722927727"
},
meta: {
order_id: "ORD-99887",
source: "inline_checkout"
}
})
});
const data = await res.json();
if (!data?.data?.checkout_url) {
alert("Failed to initialize payment");
return;
}
// Open inline checkout or redirect (depending on Inline.js behavior)
window.location.href = data.data.checkout_url;
});
</script>Currency may be one of: ETB, USD, UGX, DJF. Availability depends on payment method and region.
Backend Responsibility (Required)
Your backend must:
- Validate input (amount, currency, customer)
- Call
POST /v2/payments/hosted - Return only the necessary session data (
checkout_url) to the frontend
Never expose CHAPA_TEST_ or CHAPA_LIVE_ keys in JavaScript.
Handling Payment Results
Inline.js integrations typically expose frontend callbacks such as:
onSuccessonCloseonError
Best-practice rule: Treat frontend callbacks as UX feedback only. Never mark orders as paid based on frontend callbacks.
Payment Verification (Server-Side)
After checkout completes, your backend must verify the payment:
GET /v2/payments/{reference}/verifyVerification confirms:
- Payment actually succeeded
- Amount and currency are correct
- Transaction belongs to your merchant account
Only after verification returns success should you:
- Fulfill the order
- Grant access
- Ship goods
Webhooks (Strongly Recommended)
Webhooks provide the most reliable confirmation of payment state.
Common events:
payment.successpayment.failedpayment.cancelledpayment.incompletepayment.auth_neededpayment.blocked
Best Practices:
- Validate webhook authenticity
- Handle duplicate events safely
- Update internal transaction state
- Optionally re-verify critical payments
Common Mistakes
| Mistake | Why It's a Problem |
|---|---|
| Putting secret keys in JavaScript | Exposes your account to unauthorized access |
| Trusting inline callbacks without verification | Callbacks can be spoofed or manipulated |
| Skipping idempotency on retries | May cause duplicate charges or orders |
| Delivering value before verification | Risk of fraud and revenue loss |
Recommended Practices
- Always store
merchant_reference - Attach
meta.order_idfor reconciliation - Use idempotency keys on retries
- Use webhooks as the source of truth
- Keep frontend logic lightweight
When to Use Inline.js
Use Inline.js when you want:
- Embedded checkout experience
- Minimal UI disruption
- Faster user flow
Avoid Inline.js if you need:
- Fully custom UI
- Direct charge flows
- Deep native mobile integration
Next Steps
- Accept Payments - Full hosted payment integration
- HTML Checkout - Simple form-based checkout
- Quick Start - End-to-end payment flow