v1.0.0 REST & Webhooks

Eventro Platform API

Integrate event management, ticket inventory, server-authoritative Paystack payments, and real-time signed webhooks directly into your website or custom apps.

Overview & Architecture

The Eventro API is organized around REST conventions. All requests use standard HTTP methods, return JSON with standardized envelopes, enforce strict origin and body constraints, and provide public prefixed IDs (evt_..., tkt_..., reg_..., whe_...) so internal database sequences are never exposed.

Base URL

https://eventro.africa/api/v1

Payloads

application/json (UTF-8)

Authentication

Bearer <token>

Authentication & API Keys

Every API request must include an HTTP Authorization header containing your integration secret token. Organization tokens begin with an environment prefix:

  • evt_live_...: Live production mode (charges real cards via Paystack).
  • evt_test_...: Test / sandbox mode (allows simulation without payment gateways).
cURL Header Example
curl -X GET https://eventro.africa/api/v1/events \
  -H "Authorization: Bearer evt_live_abc123..."

Key Rotation: Eventro supports zero-downtime key rotation with grace periods. When you rotate a key from your organization developer settings, the previous key remains valid for up to 72 hours before automatic expiry.

Permission Scopes

Integrations can be restricted to minimal scopes to protect attendee data:

Scope Grants
events:read List and read event details and aggregate ticket statistics.
events:write Create, update, and upload event banners.
events:publish Publish, unpublish, and cancel events.
tickets:read / tickets:write Create and configure paid, free, or donation ticket types and limits.
registrations:read List orders and attendee records. PII (phone/email) gated to authorized callers.
registrations:write Reserve tickets and create attendee orders.
attendees:check_in Check in attendees and fetch ticket barcode QR SVG graphics.
payments:read Verify Paystack transactions and inspect payment statuses.
webhooks:read / webhooks:write Manage webhook endpoints, rotate signing keys, and trigger redeliveries.

Idempotency & Replays

All mutating endpoints (POST /registrations, POST /payment) support the Idempotency-Key header. Sending the same key within 24 hours replays the exact original response without duplicate charges or duplicate inventory reservations:

curl -X POST https://eventro.africa/api/v1/events/evt_abc/registrations \
  -H "Authorization: Bearer evt_live_..." \
  -H "Idempotency-Key: a4c89db1-331e-4509-9fc6-c4d6f8319f39" \
  -d '{"registration":{"ticket_type_id":"tkt_123","attendee_first_name":"Jane","attendee_email":"jane@example.com","quantity":1}}'

Rate Limits

API endpoints enforce per-minute sliding limits and daily quotas. Every response includes rate-limit headers:

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 298
X-RateLimit-Reset: 1735689600

Error Handling

Errors always return standard HTTP error status codes with structured JSON bodies:

{
  "error": "Validation failed: Price must be greater than or equal to 0",
  "code": "validation_failed",
  "type": "invalid_request_error",
  "details": [
    { "field": "price", "code": "greater_than_or_equal_to", "message": "Price must be greater than or equal to 0" }
  ],
  "request_id": "req_97df8ca0b1..."
}

Events Management

Complete lifecycle operations for creating, drafting, publishing, cancelling, and duplicating events:

GET /api/v1/events

List events for your organization. Supports status filtering: ?status=published|draft|past|upcoming.

POST /api/v1/events

Create a new event in draft mode. The body is wrapped as {"event": {...}} with name, category_id, event_start_date, event_location, tags and more. Send banner artwork as event.image_data_uri (base64 data URI) or a multipart event[image] file. Returns the Event object.

POST /api/v1/events/:id/publish

Validates dates, banner artwork, and ticket configuration before transitioning event to published state.

POST /api/v1/events/:id/cancel

Cancels event and halts further ticket sales.

Public Events (Catalog)

Unauthenticated, high-performance cached endpoints for public websites, apps, and ticket widgets. Caller keys are not required.

GET /api/v1/public/events

Browse the public catalog of live, published events. Filter by category, organization, or date range.

GET /api/v1/public/events/:slug_or_id

Returns published event info (including url, the public event page), on-sale tickets, and remaining capacity. Cached with HTTP ETag headers.

Ticket Types & Inventory

Configure admission categories (paid, free, donation), set sales windows, enforce min/max order quantity, and monitor real-time availability.

GET /api/v1/events/:event_id/ticket_types

Paginated list ({"object":"list","data":[...],"meta":{...}}) of ticket types with inventory metrics: total quantity, tickets sold, and remaining available capacity.

POST /api/v1/events/:event_id/ticket_types

Create a new ticket type. The body is wrapped as {"ticket_type": {...}} and accepts type (paid, free, donation), name, price, quantity, min_per_order, max_per_order, sale_starts_at, and sale_ends_at. Prices are numbers in the main currency unit (NGN, not kobo).

PATCH /api/v1/ticket_types/:id

Update ticket parameters. Note: Once tickets have been sold, commercial pricing is frozen for integrity. Capacity may only be increased.

POST /api/v1/ticket_types/:id/archive

Safely archives ticket type from public view without deleting past order history.

Orders & Registrations

Reserve tickets and manage attendee registration data. Registrations use a two-step checkout model to prevent overselling:

POST /api/v1/events/:event_id/registrations

Creates an order and reserves ticket capacity for 15 minutes. If payment is not completed before expires_at, inventory automatically returns to the available pool.

{
  "registration": {
    "ticket_type_id": "tkt_123",
    "attendee_first_name": "Jane",
    "attendee_last_name": "Doe",
    "attendee_email": "jane@example.com",
    "quantity": 1
  }
}
GET /api/v1/events/:event_id/registrations

Paginated list ({"object":"list","data":[...],"meta":{...}}) of registrations for an event. Filter by status (confirmed, pending_payment, cancelled) or check-in state.

PATCH /api/v1/registrations/:id

Update attendee contact information (name, email, phone) on an active order.

Attendee Check-In & Admission QR

Eventro automatically generates secure barcodes and SVG QR graphics for confirmed attendees, fully compatible with the Eventro onsite ticket scanner.

GET /api/v1/registrations/:id/ticket

Returns the attendee admission credential, ticket reference code, check-in status, and the raw qr_code_svg vector graphic for rendering directly in your mobile app or custom badge.

GET /api/v1/registrations/:id/ticket.pdf

Downloads the official Eventro printable PDF admission ticket badge.

POST /api/v1/registrations/:id/check_in

Admit attendee at event gate. Records timestamp and marks record as checked in. Idempotent: repeated calls indicate whether attendee was already admitted.

POST /api/v1/registrations/:id/undo_check_in

Reverses accidental check-in and resets admission status.

Server-Authoritative Paystack Checkout

Ticket pricing and fees are strictly calculated on Eventro servers. Third-party clients never submit raw prices.

  1. Reserve Order: POST /api/v1/events/:event_id/registrations creates a pending reservation and locks inventory for 15 minutes.
  2. Initialize Payment: POST /api/v1/registrations/:id/payment returns a Paystack authorization URL:
    {
      "object": "payment_session",
      "authorization_url": "https://checkout.paystack.com/access_code_123",
      "reference": "TXN-8B2F..."
    }
  3. Verify Confirmation: Once the customer completes checkout on Paystack, call POST /api/v1/registrations/:id/payment/verify to confirm the transaction and issue tickets.

Test Mode & Simulation

Events created with sandbox: true allow developers to test complete booking flows without contacting live payment processors:

curl -X POST https://eventro.africa/api/v1/registrations/reg_test_123/payment/simulate \
  -H "Authorization: Bearer evt_test_..." \
  -d '{"outcome":"success"}'

Response Shapes & Conventions

  • Money: all amounts are JSON numbers in the main currency unit (2500.0 = NGN 2,500.00, not kobo). currency is always "NGN".
  • Lists: {"object":"list","data":[...],"meta":{"page","per_page","total_count","total_pages","has_more"}}. Pagination is also in the X-Total-Count and Link headers.
  • Public event page: use the event's url field (shaped https://eventro.africa/events/<id>-<slug>); don't build it yourself.
  • Request bodies are wrapped under event, ticket_type or registration.

Ticket type

{
  "id": "tkt_abc123", "object": "ticket_type", "type": "paid",
  "name": "Regular", "price": 2500.0, "currency": "NGN",
  "effective_price": 2500.0, "early_bird_active": false,
  "quantity": 100, "remaining": 87, "sold_out": false, "on_sale": true,
  "sale_starts_at": null, "sale_ends_at": "2026-12-01T18:00:00Z",
  "min_per_order": 1, "max_per_order": 10,
  "enabled": true, "archived": false, "event_id": "evt_abc",
  "inventory": { "total": 100, "sold": 10, "reserved": 3, "remaining": 87 }
}

Registration (order)

{
  "id": "reg_abc123", "object": "registration",
  "event_id": "evt_abc", "ticket_type_id": "tkt_abc123",
  "status": "pending_payment", "payment_status": "unpaid", "quantity": 1,
  "amount": { "subtotal": 2500.0, "fees": 125.0, "total": 2625.0, "currency": "NGN" },
  "payment_reference": null, "ticket_code": null,
  "checked_in": false, "expires_at": "2026-10-15T10:30:00Z",
  "attendee": { "first_name": "Jane", "last_name": "Doe", "email": "jane@example.com", "phone": null },
  "payment_required": true,
  "next_action": "POST /api/v1/registrations/reg_abc123/payment"
}

The last two fields appear only in the create response. attendee is omitted for keys without PII access.

Webhooks & Event Delivery

Receive real-time signed HTTP POST notifications whenever events, tickets, registrations, or payments change.

Supported Event Types

event.created / event.updated / event.published / event.unpublished / event.cancelled / event.deleted
ticket_type.created / ticket_type.updated
registration.created / registration.updated / registration.cancelled / order.created
payment.pending / payment.succeeded / payment.failed / payment.refunded
ticket.issued / ticket.checked_in / attendee.updated

Payload Format

Each delivery is a JSON POST with headers Eventro-Event-Id, Eventro-Event-Type, Eventro-Delivery-Id and Eventro-Delivery-Attempt. The envelope id is identical across retries and redeliveries, so use it to de-duplicate. Payloads are deliberately minimal (no attendee personal data); fetch details through the API with the ids provided.

{
  "id": "whevt_abc123",
  "type": "payment.succeeded",
  "api_version": "v1",
  "created_at": "2026-10-15T10:20:00Z",
  "livemode": false,
  "data": {
    "object": {
      "id": "reg_abc123", "object": "registration",
      "event_id": "evt_abc", "ticket_type_id": "tkt_abc123",
      "status": "confirmed", "payment_status": "paid", "quantity": 1,
      "amount": 2625.0, "currency": "NGN",
      "payment_reference": "ref_123", "ticket_code": "A1B2C3",
      "checked_in": false
    }
  }
}

Registration objects here use a flat amount (total, main currency unit), unlike the nested amount in the API object. ticket_code is only present on ticket.* and payment.succeeded. Event and ticket_type events carry data.object with "object": "event" / "ticket_type".

Verifying Webhook Signatures

Every webhook delivery includes the Eventro-Signature header formatted as t=<timestamp>,v1=<signature>. Calculate the HMAC-SHA256 signature using your endpoint's signing secret and verify it matches within 5 minutes:

Ruby Verification Example
require "openssl"

def verify_eventro_signature(raw_payload, signature_header, signing_secret)
  parts = signature_header.split(",").to_h { |p| p.split("=", 2) }
  timestamp = parts["t"].to_i
  return false if (Time.now.to_i - timestamp).abs > 300

  expected = OpenSSL::HMAC.hexdigest("SHA256", signing_secret, "#{timestamp}.#{raw_payload}")
  Rack::Utils.secure_compare(expected, parts["v1"])
end
Node.js / Express Example
const crypto = require("crypto");

function verifyEventroSignature(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(signatureHeader.split(",").map(p => p.split("=")));
  const timestamp = parseInt(parts.t, 10);
  if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > 300) return false;

  const expected = crypto.createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}