Skip to main content
    Merchant integration guide

    Widget checkout integration

    Embed ArosaPay's protected checkout directly into your store. Buyers select a funding source, authorization completes in seconds, and you receive webhooks as the order progresses.

    3 Funding SourcesM-Pesa · Card · Wallet
    <10s AuthorizationReal-time fund securing
    Webhook-Driven4 canonical events

    How It Works

    The complete widget checkout lifecycle — from session creation to payout.

    1. Create Session

    Merchant calls POST /widget-checkout with amount, items, delivery SLA, and buyer contact.

    2. Receive Credentials

    ArosaPay returns transaction_id + payment_session for the widget.

    3. Buyer Pays

    Widget renders in your page. Buyer selects M-Pesa, Card, or Wallet and authorizes.

    4. Funds Secured

    Authorization completes in <10s. Funds are secured with buyer protection.

    5. Webhooks Fire

    Merchant receives funds.secured, delivery.marked, buyer.approved, and payout.sent events.

    ⚡ Top-Team Rule

    Your "order confirmed" logic should only trigger on the funds.secured webhook — never earlier. This ensures the buyer's payment is fully authorized before you start fulfillment.

    Quick Start

    Copy-paste integration snippets for your stack.

    Server-side: Create Session
    // server.js — Create a widget session (Node.js / Express)
    const express = require("express");
    const app = express();
    
    app.post("/create-checkout", async (req, res) => {
      const { amount, items, buyerPhone } = req.body;
    
      const response = await fetch("https://api.arosapay.com/v1/widget-checkout", {
        method: "POST",
        headers: {
          "X-API-Key": process.env.AROSAPAY_SECRET_KEY,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          amount,
          title: items[0]?.name || "Order",
          vertical: "general",
          satisfaction_days: 3,
          buyer_phone: buyerPhone,
          items,
        }),
      });
    
      const session = await response.json();
      res.json(session); // { sessionId, transactionId }
    });
    Client-side: Render Widget
    // CheckoutButton.tsx — React component
    import { useEffect, useRef } from "react";
    
    export function CheckoutButton({ amount, items, buyerPhone }) {
      const containerRef = useRef(null);
    
      const handleCheckout = async () => {
        // 1. Create session on your server
        const res = await fetch("/create-checkout", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ amount, items, buyerPhone }),
        });
        const { sessionId } = await res.json();
    
        // 2. Render the ArosaPay widget
        ArosaPay.renderCheckout("#arosapay-widget", {
          sessionId,
          onSuccess: (result) => {
            console.log("Funds secured:", result.transactionId);
            // Redirect to order confirmation
            window.location.href = "/order-confirmed";
          },
          onError: (error) => {
            console.error("Payment failed:", error.message);
          },
        });
      };
    
      return (
        <>
          <button onClick={handleCheckout}>
            Pay with ArosaPay (Protected)
          </button>
          <div id="arosapay-widget" ref={containerRef} />
        </>
      );
    }
    Test Mode

    Try it live

    Experience the checkout widget flow without writing code. Configure an order, pick a funding source, and watch the authorization complete — no real money moves.

    Order Configuration

    ArosaPay Checkoutwidget.arosapay.com

    Widget preview will appear here

    Press "Launch Widget" to start

    Webhook Events

    Four canonical events drive the entire order lifecycle.

    Sample Payload

    POST webhook payload
    {
      "id": "evt_wh_abc123",
      "event": "funds.secured",
      "created_at": "2025-02-20T14:30:00Z",
      "data": {
        "transaction_id": "txn_def456",
        "reference": "AP-2602-VKDR-7NQM",
        "amount": 15000,
        "currency": "KES",
        "funding_source": "mpesa",
        "buyer_phone": "+254712345678"
      }
    }

    HMAC-SHA256 Signature Verification

    Every webhook includes an X-Arosapay-Signature header. Always verify before processing.

    const crypto = require("crypto");
    
    function verifySignature(payload, signature, secret) {
      const expected = crypto
        .createHmac("sha256", secret)
        .update(payload, "utf8")
        .digest("hex");
      return crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expected)
      );
    }
    
    // Express.js example
    app.post("/webhooks/arosapay", (req, res) => {
      const sig = req.headers["x-arosapay-signature"];
      if (!verifySignature(JSON.stringify(req.body), sig, WEBHOOK_SECRET)) {
        return res.status(401).json({ error: "Invalid signature" });
      }
      const { event, data } = req.body;
      if (event === "funds.secured") {
        confirmOrder(data.transaction_id);
      }
      res.json({ received: true });
    });

    For the complete guide with 5 language implementations, replay protection, and debugging tools, see the Webhook Security Guide →

    Retry Policy

    Failed deliveries are retried 5 times with increasing delays: 1m → 5m → 30m → 2h → 24h. After all attempts fail, the endpoint is flagged and you receive an email alert.

    Idempotency

    Each event has a unique id. Store processed IDs and skip duplicates to ensure safe retries.

    API Reference

    Widget checkout endpoints. All requests require an X-API-Key header.

    POST
    https://api.arosapay.com/v1/widget-checkout

    Create a widget session for embedded checkout.

    Request Body
    {
      "amount": "number — Transaction amount in KES",
      "title": "string — Item/order title",
      "vertical": "string — Category (electronics, general, etc.)",
      "satisfaction_days": "number — Inspection window in days",
      "buyer_phone": "string — Buyer's phone number",
      "items?": "array — Line items [{name, qty, price}]"
    }
    Response
    {
      "sessionId": "string — Session ID for the widget",
      "transactionId": "string — ArosaPay transaction reference"
    }
    POST
    https://api.arosapay.com/v1/checkout-pay

    Initiate M-Pesa STK push payment for a session.

    Request Body
    {
      "sessionId": "string — Widget session ID",
      "phoneNumber": "string — M-Pesa number (+254...)"
    }
    Response
    {
      "paymentId": "string — Payment attempt ID",
      "status": "string — 'initiated'",
      "pollUrl": "string — URL to poll for status"
    }
    POST
    https://api.arosapay.com/v1/checkout-card-pay

    Initiate card payment via Flutterwave hosted checkout.

    Request Body
    {
      "sessionId": "string — Widget session ID",
      "buyerEmail": "string — Buyer's email address",
      "buyerName": "string — Buyer's full name"
    }
    Response
    {
      "paymentId": "string — Payment attempt ID",
      "status": "string — 'initiated'",
      "redirectUrl": "string — Flutterwave hosted page URL",
      "pollUrl": "string — URL to poll for status"
    }
    POST
    https://api.arosapay.com/v1/checkout-wallet-pay

    Pay using the buyer's ArosaPay wallet balance.

    Request Body
    {
      "sessionId": "string — Widget session ID",
      "phoneNumber": "string — Wallet owner's phone"
    }
    Response
    {
      "paymentId": "string — Payment attempt ID",
      "status": "string — 'completed' | 'failed'"
    }
    GET
    https://api.arosapay.com/v1/checkout-status?sessionId=xxx

    Poll payment status for a widget session.

    Response
    {
      "status": "string — pending | funds_securing | funds_secured | failed",
      "transactionId": "string — ArosaPay transaction ID"
    }

    Go-Live Checklist

    0/15 completed

    Testing

    Integration

    Go Live