Skip to main content
    Developer APIv1 · REST · JSON

    Programmable conditional settlement for modern commerce

    Control when money moves. REST API with M-Pesa STK Push, deterministic fund release, and real-time webhook events.

    Get API Keys
    RESTful API·Webhooks·Idempotent·Signed payloads
    create-transaction.js
    const response = await fetch("https://api.arosapay.com/v1/transactions", {
      method: "POST",
      headers: {
        "Authorization": "Bearer sk_live_...",
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        amount: 25000,
        title: "iPhone 14 Pro",
        vertical: "electronics",
        satisfaction_days: 7,
        seller_email: "seller@example.com"
      })
    });
    
    const transaction = await response.json();
    console.log(transaction.payment_link);
    Preview API response shapes
    Core Primitives

    Five building blocks for conditional payments

    Every ArosaPay integration uses these primitives. They compose into any payment flow — from simple checkout to multi-party settlement.

    Transactions

    Initiate a conditional payment. Funds are captured and held, not transferred to the merchant.

    POST /v1/transactions
    Conditions

    Define rules for fund release: delivery confirmation, inspection period, time-based triggers.

    Built into transaction creation
    Confirmations

    Buyer confirms satisfaction, or the system triggers release after the deadline.

    POST /v1/transactions/{id}/release
    Disputes

    Structured resolution with evidence, deadlines, and deterministic outcomes.

    GET /v1/disputes
    Payouts

    Funds released to seller after conditions are met. M-Pesa or bank transfer.

    GET /v1/payouts
    Quickstart

    First request in 2 minutes

    1.

    Create account

    Register as a merchant to access the developer dashboard.

    Create account →
    2.

    Get API key

    Generate test API keys from your dashboard settings.

    Go to dashboard →
    3.

    Create a transaction

    Send your first POST request to create a conditional payment.

    curl -X POST https://api.arosapay.com/v1/transactions \
      -H "Authorization: Bearer sk_test_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{
        "merchant_reference": "MERCH-ORDER-100245",
        "merchant_order_id": "ORD-100245",
        "amount": 25000,
        "currency": "KES",
        "title": "iPhone 14 Pro",
        "vertical": "electronics",
        "customer": { "name": "Jane Wanjiku", "phone": "+254712345678", "email": "jane@example.com" },
        "seller":   { "merchant_id": "merch_12345", "email": "seller@example.com" },
        "release_condition": { "type": "buyer_confirmation", "review_window_days": 7 },
        "callback_url": "https://merchant.com/webhooks/arosapay",
        "idempotency_key": "txn_ORD-100245_20260712"
      }'
    4.

    Handle webhook

    Listen for transaction.paid and transaction.completed events.

    5.

    Confirm & release funds

    Buyer confirms satisfaction, or the system auto-releases after the deadline.

    Capabilities

    API surface area

    Complete payment infrastructure for marketplaces, platforms, and applications.

    Payments
    Control Layer
    Infrastructure
    Trust

    API Reference

    Complete endpoint documentation with request, response, and error examples

    Base URLhttps://api.arosapay.com/v1
    Rate limit: 100 requests/min

    Reconciliation contract

    You send merchant_reference and merchant_order_id. We echo them back in the response and on every webhook. Store the arosapay_transaction_id alongside for support lookups.

    Idempotency

    Pass a unique idempotency_key per create attempt. Retrying with the same key returns the original transaction with 200 OK — never a duplicate.

    Authentication

    Authenticate API requests using public and secret key pairs

    Your App

    Authorization: Bearer sk_...

    ArosaPay API

    Validates & processes

    Response

    JSON payload

    Test Mode

    Development environment

    sk_test_xxxxxxxxxxxxxx

    No real payments processed. Use test phone numbers and card details. Suitable for development and staging.

    Live Mode

    Production environment

    sk_live_xxxxxxxxxxxxxx

    Real M-Pesa and card payments. Funds are transferred to recipients. Requires completed business verification.

    Authentication with error handling
    const response = await fetch("https://api.arosapay.com/v1/transactions", {
      method: "POST",
      headers: {
        "Authorization": "Bearer sk_live_your_secret_key",
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ amount: 25000, title: "Order #1234" })
    });
    
    if (!response.ok) {
      const { error } = await response.json();
      throw new Error(error.message);
    }
    
    const transaction = await response.json();

    Authentication Errors

    StatusCodeDescription
    401
    invalid_api_keyAPI key is missing, malformed, or has been revoked.
    403
    insufficient_permissionsKey lacks required scope for this operation.
    429
    rate_limit_exceededToo many requests. Retry after Retry-After header.

    Key Rotation

    Rotate API keys from your dashboard without downtime. When you rotate a key, the previous key remains valid for 24 hours.

    API Key Scopes

    read
    View transactions, payments, payouts
    write
    Create transactions, release funds, manage webhooks
    admin
    Manage API keys, configure account settings

    Integration Examples

    Integrate using any HTTP client. No proprietary SDK required.

    Setup
    # Using fetch (built-in, no dependencies required)
    # Or install node-fetch for Node.js < 18
    npm install node-fetch
    Complete Example
    const API_KEY = "sk_live_your_secret_key";
    const BASE = "https://api.arosapay.com/v1";
    
    // Create a transaction
    const createTransaction = async () => {
      const response = await fetch(`${BASE}/transactions`, {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${API_KEY}`,
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          amount: 25000,
          title: "iPhone 14 Pro",
          vertical: "electronics",
          satisfaction_days: 7,
          seller_email: "seller@example.com"
        })
      });
    
      if (!response.ok) {
        const { error } = await response.json();
        throw new Error(error.message);
      }
    
      return response.json();
    };
    
    const transaction = await createTransaction();
    console.log(transaction.payment_link);
    Webhooks · v1.1

    Webhook Events

    Real-time lifecycle events, signed with HMAC-SHA256, delivered at-least-once, and deduplicable by event_id.

    Event Types

    POST webhook bodyjson
    {
      "event_id": "evt_hg9v4d3vi4",
      "event_type": "transaction.status_changed",
      "arosapay_transaction_id": "ap_txn_9x82ksla",
      "merchant_reference": "MERCH-ORDER-100245",
      "merchant_order_id": "ORD-100245",
      "previous_status": "delivered",
      "status": "confirmed",
      "settlement_status": "approved_for_release",
      "amount": 25000,
      "currency": "KES",
      "occurred_at": "2026-08-30T04:04:56.729Z"
    }

    Signature verification

    Compute HMAC-SHA256(secret, timestamp + "." + rawBody) and compare in constant time with X-ArosaPay-Signature. Reject requests where X-ArosaPay-Timestamp is more than 5 minutes off.

    const crypto = require("crypto");
    
    // Verify against the RAW request body — do not JSON.parse first.
    function verifyArosaPaySignature(rawBody, signature, timestamp, secret) {
      const message = `${timestamp}.${rawBody}`;
      const expected = "sha256=" + crypto
        .createHmac("sha256", secret)
        .update(message, "utf8")
        .digest("hex");
      return crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expected)
      );
    }
    
    app.post("/webhooks/arosapay",
      express.raw({ type: "application/json" }),
      async (req, res) => {
        const signature = req.headers["x-arosapay-signature"];
        const timestamp = req.headers["x-arosapay-timestamp"];
        const eventId   = req.headers["x-arosapay-event-id"];
        const rawBody   = req.body.toString("utf8");
    
        // Reject stale deliveries (>5 minutes) — protects against replay.
        if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
          return res.status(400).send("Stale timestamp");
        }
    
        if (!verifyArosaPaySignature(rawBody, signature, timestamp, process.env.AROSAPAY_WEBHOOK_SECRET)) {
          return res.status(401).send("Invalid signature");
        }
    
        // Deduplicate: at-least-once delivery.
        if (await seen(eventId)) return res.status(200).send("ok");
        await markSeen(eventId);
    
        const event = JSON.parse(rawBody);
        // Reconcile by merchant_reference / merchant_order_id, NOT arosapay_transaction_id alone.
        await reconcile(event.merchant_order_id, event.status, event.settlement_status);
        res.status(200).send("ok");
      }
    );

    At-least-once delivery

    Retries run at 60s, 5m, 30m, and 2h. Deduplicate by storing every event_id you have already processed; return 200 on repeats so retries stop.

    Reconciliation

    Match events by merchant_order_id or merchant_reference — these are the values your system supplied at create time. Store arosapay_transaction_id alongside for support.

    Replay protection

    Reject any request where X-ArosaPay-Timestamp differs from your clock by more than 5 minutes.

    IP allowlist

    Optionally restrict incoming webhooks to ArosaPay egress ranges:

    154.159.237.0/24, 41.215.96.0/24

    Lifecycle states (status)

    reserved
    Funds captured and safeguarded, waiting for delivery
    delivered
    Seller marked the order as delivered; review window is running
    confirmed
    Buyer confirmed satisfaction; funds are approved for release
    released
    Funds released to the seller
    disputed
    Buyer opened a dispute
    under_review
    Case is being reviewed by ArosaPay
    refunded
    Funds were refunded to the buyer
    cancelled
    Transaction was cancelled before payment
    expired
    Review window ended without a decision

    Settlement statuses (settlement_status)

    pending_confirmation
    Funds held; waiting on buyer confirmation or delivery
    under_review
    Frozen pending dispute or manual review
    approved_for_release
    Cleared for release; payout is being scheduled
    released
    Paid out to the seller
    refund_pending
    Refund initiated, not yet completed
    refunded
    Refund completed
    failed
    Cancelled, expired, or otherwise not settlable
    reversed
    Reversal received from the payment rail
    Use Cases

    What you can build

    Marketplaces

    Protect buyers and sellers with conditional settlement on every order. Funds release only after buyer confirmation.

    COD replacement

    Replace cash-on-delivery with digital payment protection. Reduce rejection rates and failed deliveries.

    High-value transactions

    Electronics, vehicles, real estate — built-in inspection periods with structured dispute resolution.

    Logistics-linked payments

    Release funds on delivery confirmation from your logistics provider. Integrate via webhook or API.

    Built for Reliability

    Infrastructure you can depend on

    Availability

    Automatic failover and health monitoring across application and API tiers.

    Idempotency

    Every mutation accepts an idempotency key. Safe to retry without duplicate side effects.

    Webhook retries

    3 retries with exponential backoff. Failed endpoints flagged automatically with email alerts.

    Key rotation

    Rotate API keys from the dashboard with a 24-hour grace period. Zero downtime migrations.

    Resources

    Developer resources

    System Status

    Real-time API uptime and performance monitoring

    Developer Support

    Technical assistance from the integration team