Transactions

Transaction Timeline

Retrieve the full event history for a transaction, ordered from earliest to latest.

Transaction Timeline

GET /v1/transactions/:reference/timeline

Returns an ordered list of events recorded against a transaction — from the moment the session was opened through to payment completion, failure, or cancellation.


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/PAB12CD3420260813/timeline \
  -H "Authorization: Bearer sk_live_your_key_here"
import { Epay } from '@e-pay/node';

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

const timeline = await epay.transactions.timeline(reference);

for (const event of timeline.events) {
console.log(event.eventType, event.occurredAt, event.errorCode);
}
const res = await fetch('https://api.epayethiopia.com/v1/transactions/PAB12CD3420260813/timeline', {
  headers: { Authorization: `Bearer ${process.env.EPAY_SECRET_KEY}` },
});

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

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

epay = Epay() # reads EPAY_SECRET_KEY

timeline = epay.transactions.timeline(reference)

for event in timeline["events"]:
print(event["eventType"], event["occurredAt"], event["errorCode"])
import os
import requests

res = requests.get(
    "https://api.epayethiopia.com/v1/transactions/PAB12CD3420260813/timeline",
    headers={"Authorization": f"Bearer {os.environ['EPAY_SECRET_KEY']}"},
    timeout=30,
)
res.raise_for_status()

timeline = res.json()
use Epay\Epay;

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

$timeline = $epay->transactions->timeline($reference);

foreach ($timeline['events'] as $event) {
echo $event['eventType'], ' ', $event['occurredAt'], PHP_EOL;
}
$ch = curl_init('https://api.epayethiopia.com/v1/transactions/PAB12CD3420260813/timeline');

curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('EPAY_SECRET_KEY')],
]);

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

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

$timeline = json_decode($body, true);
timeline, err := client.Transactions.Timeline(ctx, reference)
if err != nil {
	return err
}

for \_, event := range timeline.Events {
log.Println(event.EventType, event.OccurredAt)
}
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.epayethiopia.com/v1/transactions/PAB12CD3420260813/timeline", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("EPAY_SECRET_KEY"))

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 timeline struct {
	Reference string `json:"reference"`
	Events    []struct {
		EventType    string  `json:"eventType"`
		OccurredAt   string  `json:"occurredAt"`
		ErrorCode    *string `json:"errorCode"`
		ErrorMessage *string `json:"errorMessage"`
	} `json:"events"`
}
if err := json.NewDecoder(res.Body).Decode(&timeline); err != nil {
	return err
}
Epay epay = Epay.fromEnvironment(); // reads EPAY_SECRET_KEY

Timeline timeline = epay.transactions().timeline(reference);

for (TimelineEvent event : timeline.events()) {
System.out.println(event.eventType() + " " + event.occurredAt() + " " + event.errorCode());
}
HttpClient http = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder(
                URI.create("https://api.epayethiopia.com/v1/transactions/PAB12CD3420260813/timeline"))
        .header("Authorization", "Bearer " + System.getenv("EPAY_SECRET_KEY"))
        .GET()
        .build();

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

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

// {"reference":"…","events":[{"eventType":"…","occurredAt":"…",…}]}
String timeline = response.body();
import { Controller, Get, Param } from '@nestjs/common';
import { EpayService } from '@e-pay/nestjs';

@Controller('payments')
export class PaymentsController {
constructor(private readonly epay: EpayService) {}

@Get(':reference/timeline')
timeline(@Param('reference') reference: string) {
return this.epay.transactions.timeline(reference);
}
}
from django.http import JsonResponse
from django.views.decorators.http import require_GET

from .clients import epay  # Epay(api_key=settings.EPAY_SECRET_KEY)


@require_GET
def payment_timeline(request, reference):
    return JsonResponse(epay.transactions.timeline(reference))
from flask import jsonify

from app import app, epay # epay = Epay(), one per process

@app.get("/payments/<reference>/timeline")
def payment_timeline(reference):
return jsonify(epay.transactions.timeline(reference))
from app.main import app, epay  # epay = AsyncEpay(), closed in lifespan


@app.get("/payments/{reference}/timeline")
async def payment_timeline(reference: str):
    return await epay.transactions.timeline(reference)
use Epay\Epay;
use Illuminate\Http\JsonResponse;

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

    public function timeline(string $reference): JsonResponse
    {
        return response()->json($this->epay->transactions->timeline($reference));
    }

}

// routes/web.php
// Route::get('/payments/{reference}/timeline', [PaymentController::class, 'timeline']);
@RestController
@RequestMapping("/payments")
class PaymentController {
    private final Epay epay;

    PaymentController(Epay epay) {
        this.epay = epay;
    }

    @GetMapping("/{reference}/timeline")
    Timeline timeline(@PathVariable String reference) {
        return epay.transactions().timeline(reference);
    }
}

Completed payment

{
  "reference": "PAB12CD3420260813",
  "events": [
    {
      "eventType": "session_created",
      "occurredAt": "2026-08-13T11:30:00.000Z",
      "errorCode": null,
      "errorMessage": null
    },
    {
      "eventType": "payment_initiated",
      "occurredAt": "2026-08-13T11:45:12.000Z",
      "errorCode": null,
      "errorMessage": null
    },
    {
      "eventType": "payment_completed",
      "occurredAt": "2026-08-13T11:47:00.000Z",
      "errorCode": null,
      "errorMessage": null
    }
  ]
}

Failed payment

{
  "reference": "PFF98AB1220260812",
  "events": [
    {
      "eventType": "session_created",
      "occurredAt": "2026-08-12T09:15:00.000Z",
      "errorCode": null,
      "errorMessage": null
    },
    {
      "eventType": "payment_failed",
      "occurredAt": "2026-08-12T09:18:44.000Z",
      "errorCode": "INSUFFICIENT_FUNDS",
      "errorMessage": "The customer's account does not have sufficient balance."
    }
  ]
}

Path Parameters

Prop

Type


Response

Prop

Type

Event object

Prop

Type


Errors

StatusDescription
401Missing or invalid API key.
404No transaction found for the given reference under your account.

Next: Webhooks — receive payment events in real time instead of polling.

On this page