Smart Payment Infrastructure

TRAPAY API v2

Flexible payment infrastructure software for modern online businesses.
Route payments, automate workflows, manage provider integrations, monitor transactions and reconcile payment data through one secure API.

Introduction

Welcome to the Trapay API documentation. Our REST API allows you to process transactions, and manage payments programmatically.

RESTful Design

Predictable resource-oriented URLs and standard HTTP methods

JSON Responses

All responses are returned in JSON format with clear schemas

Webhooks

Real-time notifications for transaction events

Authentication Flow

Merchants authenticate by including their public API key in the request body.

Request Structure

POST /api/endpoint
Content-Type: application/json

{
  "public_key": "pk_...",
  "other_params": "..."
}

Where to Get API Key

1

Navigate to Integration

Dashboard → Integration → API Keys section

app.trapay.uk/dashboard/integration

Key Revoke

1

Navigate to Settings

Dashboard → Settings → Security → API Keys section

app.trapay.uk/dashboard/settings?tab=security
2

Revoke Keys

Press on Revoke all API keys button to invalidate all existing keys.

Key Types

Public Key
pk_...

For API authentication

Secret Key
sk_...

For webhook signature validation

Create Payment

POST /api/payments/create

Public endpoint for creating payment requests.

Authentication

Validates merchant via public_key in request body.

Code Examples

const response = await fetch('https://api.trapay.uk/api/payments/create', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    public_key: 'pk_your_public_key',
    gateway: '0000',
    amount: 100.00,
    currency: 'USD',
    order_id: 'ORDER-123'
  })
});

const data = await response.json();
console.log(data);
import requests

url = 'https://api.trapay.uk/api/payments/create'
payload = {
    'public_key': 'pk_your_public_key',
    'gateway': '0000',
    'amount': 100.00,
    'currency': 'USD',
    'order_id': 'ORDER-123'
}

response = requests.post(url, json=payload)
data = response.json()
print(data)
<?php

$url = 'https://api.trapay.uk/api/payments/create';
$payload = [
    'public_key' => 'pk_your_public_key',
    'gateway' => '0000',
    'amount' => 100.00,
    'currency' => 'USD',
    'order_id' => 'ORDER-123'
];

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json'
]);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);
print_r($data);
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
)

func main() {
    url := "https://api.trapay.uk/api/payments/create"
    
    payload := map[string]interface{}{
        "public_key": "pk_your_public_key",
        "gateway": "0000",
        "amount": 100.00,
        "currency": "USD",
        "order_id": "ORDER-123",
    }
    
    jsonData, _ := json.Marshal(payload)
    resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    
    var result map[string]interface{}
    json.NewDecoder(resp.Body).Decode(&result)
    fmt.Println(result)
}

Request Body

Parameter Type Required Description
public_key string Required Merchant's public key from database
gateway string Required Gateway ID (e.g. "0000")
amount number Required Payment amount (positive)
currency string Required Payment currency (USD, EUR, etc.)
order_id string Required Merchant's order reference

Optional Fields

Parameter Type Description
success_url string Redirect URL on success
fail_url string Redirect URL on failure
pending_url string Redirect URL on pending
customer_email string Customer email address
customer_name string Customer full name

Success Response

{
  "success": true,
  "message": "Payment created successfully",
  "result": {
    "id": "pre_payment_uuid",
    "order_id": "ORDER-123",
    "proxy_url": "https://app.trapay.uk/p/{pre_payment_id}",
    "status": "PENDING_CUSTOMER_DETAILS"
  }
}

Error Response

{
  "success": false,
  "message": "Invalid public key"
}

Validation errors include a details array:

{
  "success": false,
  "message": "Validation error",
  "details": [
    "\"gateway\" is required"
  ]
}

Common Error Messages

Invalid public key Public key not found
Shop is not active Shop status is not ACTIVE
Gateway "…" is not enabled for your shop Gateway not enabled for shop
Validation error Request body failed validation (see details)

Payment Statuses

Main Flow

PENDING
Awaiting payment
PROCESSING
Payment in progress

Alternative Outcomes

FAILED
Payment declined or error
EXPIRED
Payment timeout reached

Post-Payment Actions

REFUND
Payment reversed by merchant
CHARGEBACK
Disputed by customer

Webhooks

Webhooks are HTTP callbacks sent to merchant's configured URL when payment status changes.

Configuration

Merchants configure webhooks in Dashboard → Integration → Webhooks:

{
  "webhookUrl": "https://yoursite.com/webhook",
  "webhookEvents": [
    "payment.success",
    "payment.failed",
    "payment.pending"
  ]
}

Event Mapping

Each webhook includes an event field. Enable the events you need in Dashboard → Integration → Webhooks.

Event Payment status
payment.success PAID
payment.failed FAILED, EXPIRED, CHARGEBACK, REFUND
payment.pending PENDING, PROCESSING

Webhook Payload

Request Format

POST {merchant.webhookUrl}
Content-Type: application/json
X-Webhook-Signature: {hmac_signature}

{
  "event": "payment.success",
  "payment": {
    "id": "cmr0v4rjr00kvg0vfx5nywo14",
    "orderId": "ORDER-123",
    "status": "PAID",
    "amount": 100.00,
    "currency": "EUR",
    "createdAt": "2024-01-15T10:25:00.000Z",
    "paidAt": "2024-01-15T10:30:00.000Z"
  }
}

Payment Object Fields

Field Type Description
id string Trapay payment ID
orderId string Your order reference from the create request
status string Payment status: PAID, FAILED, PENDING, etc.
amount number Payment amount
currency string Payment currency (USD, EUR, etc.)
createdAt string ISO 8601 creation timestamp
paidAt string | null ISO 8601 paid timestamp, or null if not paid yet

Test Webhook

Use Dashboard → Integration → Send Test Webhook to verify your endpoint. The test payload uses event: "test":

{
  "event": "test",
  "payment": {
    "id": "test_payment_123",
    "orderId": "test_order_123",
    "status": "PAID",
    "amount": 100,
    "currency": "USD",
    "createdAt": "2024-01-15T10:25:00.000Z",
    "paidAt": "2024-01-15T10:30:00.000Z"
  }
}

Signature Verification

Webhook signature uses HMAC-SHA256 with your secret key (sk_...) to ensure authenticity.

Signature Algorithm

Sign the raw request body exactly as received — do not re-serialize parsed JSON, or the signature will not match.

const signature = crypto
  .createHmac('sha256', secret_key)
  .update(rawRequestBody)
  .digest('hex')

Verification Examples

import crypto from 'crypto'
import express from 'express'

const app = express()

// Keep raw body for signature verification
app.use('/webhook', express.raw({ type: 'application/json' }))

export const verifyWebhookSignature = (
  payload: Buffer | string,
  signature: string,
  secret: string
): boolean => {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  )
}

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-webhook-signature'] as string
  const secretKey = 'sk_your_secret_key'

  if (!verifyWebhookSignature(req.body, signature, secretKey)) {
    return res.status(401).json({ error: 'Invalid signature' })
  }

  const data = JSON.parse(req.body.toString())
  console.log('Webhook verified:', data)
  res.status(200).json({ received: true })
})
import hmac
import hashlib
from flask import Flask, request, jsonify

app = Flask(__name__)

def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(signature, expected_signature)

@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Webhook-Signature', '')
    payload = request.get_data()
    secret_key = 'sk_your_secret_key'
    
    if not verify_webhook_signature(payload, signature, secret_key):
        return jsonify({'error': 'Invalid signature'}), 401
    
    data = request.get_json()
    print('Webhook verified:', data)
    return jsonify({'received': True}), 200
<?php

function verifyWebhookSignature($payload, $signature, $secret) {
    $expectedSignature = hash_hmac('sha256', $payload, $secret);
    return hash_equals($signature, $expectedSignature);
}

$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$secretKey = 'sk_your_secret_key';

if (!verifyWebhookSignature($payload, $signature, $secretKey)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

$data = json_decode($payload, true);
error_log('Webhook verified: ' . print_r($data, true));

http_response_code(200);
echo json_encode(['received' => true]);
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "io"
    "net/http"
)

func verifyWebhookSignature(payload, signature, secret string) bool {
    h := hmac.New(sha256.New, []byte(secret))
    h.Write([]byte(payload))
    expectedSignature := hex.EncodeToString(h.Sum(nil))
    return hmac.Equal([]byte(signature), []byte(expectedSignature))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
    signature := r.Header.Get("X-Webhook-Signature")
    secretKey := "sk_your_secret_key"
    
    body, _ := io.ReadAll(r.Body)
    payload := string(body)
    
    if !verifyWebhookSignature(payload, signature, secretKey) {
        http.Error(w, `{"error":"Invalid signature"}`, http.StatusUnauthorized)
        return
    }
    
    var data map[string]interface{}
    json.Unmarshal(body, &data)
    
    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(map[string]bool{"received": true})
}

func main() {
    http.HandleFunc("/webhook", webhookHandler)
    http.ListenAndServe(":8080", nil)
}

Delivery

Trapay sends one HTTP POST when a payment status changes. If delivery fails (network error or non-2xx response), the attempt is logged but not automatically retried.

Single Attempt

One delivery per status change event

Success Criteria

HTTP 200–299 response

Webhook Logs

View delivery history in Dashboard → Integration → Webhook Logs

Failure Handling

timeout Your endpoint did not respond in time
connection_refused Unable to connect to webhook URL
invalid_status HTTP status code outside 200–299 range

Failed Webhooks

If a webhook fails, check Dashboard → Integration → Webhook Logs for the HTTP status and response. Use Send Test Webhook to verify your endpoint and signature handling before going live.