Payments

Initialize a Payment

Create a payment session and receive a hosted checkout URL to redirect your customer.

Initialize a Payment

POST /v1/transactions/initialize

Creates a payment session and returns a hosted checkout URL. Redirect your customer to checkoutUrl to complete the payment.

Use sk_test_… for sandbox, sk_live_… for production. The mode is inferred from the key — no separate config needed.


Request

raw HTTP
npm install @e-pay/node
npm install @e-pay/nestjs
pip install epay
pip install epay
pip install epay
pip install epay
composer require epay-et/php-sdk
composer require epay-et/php-sdk
implementation("com.epayethiopia:epay-java:0.1.0")
implementation("com.epayethiopia:epay-spring-boot-starter:0.1.0")
go get github.com/epay-et/go-sdk

Nothing NestJS-specific here — this is the same Node.js client call.

Nothing Django-specific here — this is the same Python client call.

Nothing Flask-specific here — this is the same Python client call.

Nothing FastAPI-specific here — this is the same Python client call.

Nothing Laravel-specific here — this is the same PHP client call.

Nothing Spring Boot-specific here — this is the same Java client call.

curl https://api.epayethiopia.com/v1/transactions/initialize \
  -X POST \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -H "x-idempotency-key: order_123" \
  -d '{
    "amount": "250.00",
    "currencyCode": "ETB",
    "customerPhone": "+251911234567",
    "merchantReference": "order_123",
    "returnUrl": "https://yourstore.com/order/123/complete"
  }'
import { Epay } from '@e-pay/node';

const epay = new Epay({ apiKey: process.env.EPAY_SECRET_KEY });

const session = await epay.payments.initialize(
{
amount: '250.00',
currencyCode: 'ETB',
customerPhone: '+251911234567',
merchantReference: 'order_123',
returnUrl: 'https://yourstore.com/order/123/complete',
},
{ idempotencyKey: 'order_123' },
);

// session.reference, session.checkoutUrl, session.expiresAt
redirect(session.checkoutUrl);
const res = await fetch('https://api.epayethiopia.com/v1/transactions/initialize', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.EPAY_SECRET_KEY}`,
    'Content-Type': 'application/json',
    'x-idempotency-key': 'order_123',
  },
  body: JSON.stringify({
    amount: '250.00',
    currencyCode: 'ETB',
    customerPhone: '+251911234567',
    merchantReference: 'order_123',
    returnUrl: 'https://yourstore.com/order/123/complete',
  }),
});

if (!res.ok) throw new Error(`ePay ${res.status}: ${await res.text()}`);

const { reference, checkoutUrl, expiresAt } = await res.json();
from epay import Epay

epay = Epay() # reads EPAY_SECRET_KEY

session = epay.payments.initialize(
amount="250.00",
currency_code="ETB",
customer_phone="+251911234567",
merchant_reference="order_123",
return_url="https://yourstore.com/order/123/complete",
idempotency_key="order_123",
)

# session["reference"], session["checkoutUrl"], session["expiresAt"]

redirect(session["checkoutUrl"])
import os
import requests

res = requests.post(
    "https://api.epayethiopia.com/v1/transactions/initialize",
    headers={
        "Authorization": f"Bearer {os.environ['EPAY_SECRET_KEY']}",
        "x-idempotency-key": "order_123",
    },
    json={
        "amount": "250.00",
        "currencyCode": "ETB",
        "customerPhone": "+251911234567",
        "merchantReference": "order_123",
        "returnUrl": "https://yourstore.com/order/123/complete",
    },
    timeout=30,
)
res.raise_for_status()

session = res.json()
reference = session["reference"]
checkout_url = session["checkoutUrl"]
use Epay\Epay;

$epay = new Epay(); // reads EPAY_SECRET_KEY

$session = $epay->payments->initialize([
'amount' => '250.00',
'currencyCode' => 'ETB',
'customerPhone' => '+251911234567',
'merchantReference' => 'order_123',
'returnUrl' => 'https://yourstore.com/order/123/complete',
], idempotencyKey: 'order_123');

// $session['reference'], $session['checkoutUrl'], $session['expiresAt']
header('Location: ' . $session['checkoutUrl']);
$ch = curl_init('https://api.epayethiopia.com/v1/transactions/initialize');

curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('EPAY_SECRET_KEY'),
        'Content-Type: application/json',
        'x-idempotency-key: order_123',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'amount' => '250.00',
        'currencyCode' => 'ETB',
        'customerPhone' => '+251911234567',
        'merchantReference' => 'order_123',
        'returnUrl' => 'https://yourstore.com/order/123/complete',
    ]),
]);

$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($status >= 400) {
    throw new RuntimeException("ePay {$status}: {$body}");
}

$session = json_decode($body, true);
client, err := epay.NewClient() // reads EPAY_SECRET_KEY
if err != nil {
	return err
}

session, err := client.Payments.Initialize(ctx, epay.InitializeParams{
Amount: "250.00",
CurrencyCode: "ETB",
CustomerPhone: "+251911234567",
MerchantReference: "order_123",
ReturnURL: "https://yourstore.com/order/123/complete",
IdempotencyKey: "order_123",
})
if err != nil {
return err
}

// session.Reference, session.CheckoutURL, session.ExpiresAt
http.Redirect(w, r, session.CheckoutURL, http.StatusSeeOther)
payload, _ := json.Marshal(map[string]string{
	"amount":            "250.00",
	"currencyCode":      "ETB",
	"customerPhone":     "+251911234567",
	"merchantReference": "order_123",
	"returnUrl":         "https://yourstore.com/order/123/complete",
})

req, _ := http.NewRequestWithContext(ctx, http.MethodPost,
	"https://api.epayethiopia.com/v1/transactions/initialize", bytes.NewReader(payload))
req.Header.Set("Authorization", "Bearer "+os.Getenv("EPAY_SECRET_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-idempotency-key", "order_123")

res, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer res.Body.Close()

if res.StatusCode >= 400 {
	return fmt.Errorf("ePay %d", res.StatusCode)
}

var session struct {
	Reference   string `json:"reference"`
	CheckoutURL string `json:"checkoutUrl"`
	ExpiresAt   string `json:"expiresAt"`
}
if err := json.NewDecoder(res.Body).Decode(&session); err != nil {
	return err
}
import com.epayethiopia.epay.Epay;
import com.epayethiopia.epay.InitializeParams;
import com.epayethiopia.epay.model.InitializedPayment;

Epay epay = Epay.fromEnvironment(); // reads EPAY_SECRET_KEY; thread-safe, share one

InitializedPayment session = epay.payments().initialize(InitializeParams.builder()
.amount("250.00")
.currencyCode("ETB")
.customerPhone("+251911234567")
.merchantReference("order_123")
.returnUrl("https://yourstore.com/order/123/complete")
.idempotencyKey("order_123")
.build());

// session.reference(), session.checkoutUrl(), session.expiresAt()
response.sendRedirect(session.checkoutUrl());
HttpClient http = HttpClient.newHttpClient();

String body = """
        {
          "amount": "250.00",
          "currencyCode": "ETB",
          "customerPhone": "+251911234567",
          "merchantReference": "order_123",
          "returnUrl": "https://yourstore.com/order/123/complete"
        }
        """;

HttpRequest request = HttpRequest.newBuilder(
                URI.create("https://api.epayethiopia.com/v1/transactions/initialize"))
        .header("Authorization", "Bearer " + System.getenv("EPAY_SECRET_KEY"))
        .header("Content-Type", "application/json")
        .header("x-idempotency-key", "order_123")
        .POST(HttpRequest.BodyPublishers.ofString(body))
        .build();

HttpResponse<String> response = http.send(request, HttpResponse.BodyHandlers.ofString());

if (response.statusCode() >= 400) {
    throw new IllegalStateException("ePay " + response.statusCode() + ": " + response.body());
}

// {"reference":"…","checkoutUrl":"…","status":"success","expiresAt":"…"}
String session = response.body();
// app.module.ts, once: EpayModule.forRoot({ isGlobal: true }) reads EPAY_SECRET_KEY

import { Controller, Param, Post, Redirect } from '@nestjs/common';
import { EpayService } from '@e-pay/nestjs';

@Controller('orders')
export class CheckoutController {
constructor(
private readonly epay: EpayService,
private readonly orders: OrdersService,
) {}

@Post(':id/pay')
@Redirect()
async pay(@Param('id') id: string) {
const order = await this.orders.find(id);

    const session = await this.epay.payments.initialize(
      {
        amount: order.total,
        currencyCode: 'ETB',
        customerPhone: order.customerPhone,
        merchantReference: order.id,
        returnUrl: `https://yourstore.com/orders/${order.id}/complete`,
      },
      { idempotencyKey: order.id },
    );

    return { url: session.checkoutUrl, statusCode: 303 };

}
}
# payments/clients.py — one client per process; it pools connections
from django.conf import settings
from epay import Epay

epay = Epay(api_key=settings.EPAY_SECRET_KEY)


# payments/views.py
from django.shortcuts import get_object_or_404, redirect
from django.views.decorators.http import require_POST

from .clients import epay
from .models import Order


@require_POST
def pay(request, order_id):
    order = get_object_or_404(Order, pk=order_id)

    session = epay.payments.initialize(
        amount=order.total,  # a DecimalField stays exact
        currency_code="ETB",
        customer_phone=order.customer_phone,
        merchant_reference=str(order.pk),
        return_url=request.build_absolute_uri(f"/orders/{order.pk}/complete"),
        idempotency_key=str(order.pk),
    )

    return redirect(session["checkoutUrl"])
from flask import Flask, redirect, url_for
from epay import Epay

app = Flask(**name**)
epay = Epay() # one client per process; reads EPAY_SECRET_KEY

@app.post("/orders/<order_id>/pay")
def pay(order_id):
order = Order.query.get_or_404(order_id)

    session = epay.payments.initialize(
        amount=order.total,
        currency_code="ETB",
        customer_phone=order.customer_phone,
        merchant_reference=order_id,
        return_url=url_for("order_complete", order_id=order_id, _external=True),
        idempotency_key=order_id,
    )

    return redirect(session["checkoutUrl"], code=303)
from contextlib import asynccontextmanager

from fastapi import FastAPI
from fastapi.responses import RedirectResponse
from epay import AsyncEpay

epay = AsyncEpay()  # reads EPAY_SECRET_KEY


@asynccontextmanager
async def lifespan(app: FastAPI):
    yield
    await epay.aclose()  # release the connection pool on shutdown


app = FastAPI(lifespan=lifespan)


@app.post("/orders/{order_id}/pay")
async def pay(order_id: str):
    order = await orders.get(order_id)

    session = await epay.payments.initialize(
        amount=order.total,
        currency_code="ETB",
        customer_phone=order.customer_phone,
        merchant_reference=order_id,
        return_url=f"https://yourstore.com/orders/{order_id}/complete",
        idempotency_key=order_id,
    )

    return RedirectResponse(session["checkoutUrl"], status_code=303)
// The service provider is auto-discovered and reads EPAY_SECRET_KEY from .env.

use App\Models\Order;
use Epay\Epay;
use Illuminate\Http\RedirectResponse;

final class CheckoutController
{
public function \_\_construct(private readonly Epay $epay) {}

    public function store(Order $order): RedirectResponse
    {
        $session = $this->epay->payments->initialize([
            'amount' => $order->total,
            'currencyCode' => 'ETB',
            'customerPhone' => $order->customer_phone,
            'merchantReference' => (string) $order->id,
            'returnUrl' => route('orders.complete', $order),
        ], idempotencyKey: (string) $order->id);

        return redirect()->away($session['checkoutUrl']);
    }

}

// routes/web.php
// Route::post('/orders/{order}/pay', [CheckoutController::class, 'store']);
// application.yml
//   epay:
//     api-key: ${EPAY_SECRET_KEY}

@RestController
class CheckoutController {
    private final Epay epay;
    private final Orders orders;

    CheckoutController(Epay epay, Orders orders) {
        this.epay = epay;
        this.orders = orders;
    }

    @PostMapping("/orders/{id}/pay")
    ResponseEntity<Void> pay(@PathVariable String id) {
        Order order = orders.find(id);

        InitializedPayment session = epay.payments().initialize(InitializeParams.builder()
                .amount(order.total())
                .currencyCode("ETB")
                .customerPhone(order.phone())
                .merchantReference(order.id())
                .returnUrl("https://yourstore.com/orders/" + order.id() + "/complete")
                .idempotencyKey(order.id())
                .build());

        return ResponseEntity.status(HttpStatus.SEE_OTHER)
                .location(URI.create(session.checkoutUrl()))
                .build();
    }
}

Response

{
  "reference": "PAB12CD3420260813",
  "checkoutUrl": "https://checkout.epayethiopia.com/pay/PAB12CD3420260813",
  "status": "success",
  "expiresAt": "2026-08-13T12:30:00.000Z"
}

Headers

Prop

Type


Body

Prop

Type


Response

Prop

Type


Errors

StatusDescription
400Invalid amount value or unrecognized currencyCode.
401Missing or invalid API key.
404No payment source found for your account — contact support.
429Rate limit exceeded.

Next: Verify a Payment — confirm completion before fulfilling an order.

On this page