Home

Booking System API

version 1

The Booking System API allows you to generate, confirm, handle, cancel, modify, and receive booking information directly.

OpenAPI Specification

Start designing your API in minutes. The OpenAPI specification file enables you to learn and interact with API elements, including all available endpoints, input and output representations.

General information

Booking System API is organized around REST.
Our API has predictable resource-oriented URLs, accepts form-encoded request bodies, returns JSON-encoded responses, and uses standard HTTP response codes, authentication, and verbs.

API endpoint#

1https://rates.searates.com/api/v1/bookings

All endpoints are only accessible via HTTPS and are located at rates.searates.com

Retrieve Bearer Token

To access Booking System API, you first need to obtain a bearer token.

Endpoint:

1GET https://www.searates.com/auth/platform-token

Required Parameters:

ParameterDescription
idYour platform ID, provided by your SeaRates manager
api_keyYour API key, provided by your SeaRates manager
loginYour SeaRates website login
passwordYour SeaRates website password

Example Request:

1GET https://www.searates.com/auth/platform-token?id=PLATFORM_ID&api_key=API_KEY&login=YOUR_LOGIN&password=YOUR_PASSWORD

Usage:

Include the token in the Authorization header for subsequent API requests:

1Authorization: Bearer 123

Create new booking

post/create
Headers

Content-Typeapplication/json

Body Parameters
  • shipment_idREQUIREDinteger
  • shipping_typeREQUIREDno type

    Allowed values:fcllclbulkland_fclftlltlfwllwlrail_fclrail_ftlair

  • ready_to_loadREQUIREDstring
  • partiesREQUIREDobject

    List of parties involved in the booking. The object may include up to three party objects: shipper, consignee, and notify (used for Shipping Instructions). Each party object contains the contact and company details required for the booking documentation.

  • productOptionalobject

    Product and cargo details used for creating the booking.

Response example
Booking data, retrieved after update
1{
2  "id": 0,
3  "shipment_id": 0,
4  "shipping_type": "string",
5  "created_at": "2026-08-24T14:15:22Z",
6  "ready_to_load": "2026-08-24T14:15:22Z",
7  "origin": {
8    "id": 0,
9    "name": "string",
10    "short_name": "string",
11    "country": "string",
12    "latitude": "string",
13    "longitude": "string",
14    "point_type": "string"
15  },
16  "destination": {
17    "id": 0,
18    "name": "string",
19    "short_name": "string",
20    "country": "string",
21    "latitude": "string",
22    "longitude": "string",
23    "point_type": "string"
24  },
25  "status": {
26    "id": 0,
27    "code": "string",
28    "name": "string",
29    "description": "string",
30    "global_name": "string",
31    "global_code": "string"
32  },
33  "parties": {
34    "shipper": {
35      "first_name": "string",
36      "last_name": "string",
37      "email": "string",
38      "phone": "string",
39      "company_name": "string",
40      "country": "string",
41      "city": "string",
42      "address": "string",
43      "zip_code": "string",
44      "vat_number": 0,
45      "freight_forwarder_reference": "string"
46    },
47    "consignee": {
48      "first_name": "string",
49      "last_name": "string",
50      "email": "string",
51      "phone": "string",
52      "company_name": "string",
53      "country": "string",
54      "city": "string",
55      "address": "string",
56      "zip_code": "string",
57      "vat_number": 0,
58      "freight_forwarder_reference": "string"
59    },
60    "notify": {
61      "first_name": "string",
62      "last_name": "string",
63      "email": "string",
64      "phone": "string",
65      "company_name": "string",
66      "country": "string",
67      "city": "string",
68      "address": "string",
69      "zip_code": "string",
70      "vat_number": 0,
71      "freight_forwarder_reference": "string"
72    }
73  },
74  "particulars": [
75    {
76      "id": 0,
77      "container": {},
78      "seal_number": "string",
79      "package_type_id": 0,
80      "package_quantity": 0,
81      "net_weight": 0,
82      "gross_weight": 0,
83      "gross_volume": 0,
84      "imo_class": "string",
85      "cargo_description": "string"
86    }
87  ],
88  "evgm": {
89    "id": 0,
90    "approval_signature": "string",
91    "approval_date": "2026-08-24",
92    "notify_email": "[email protected]",
93    "items": [
94      {
95        "container": {}
96      }
97    ]
98  }
99}

Update booking

patch/{bookingId}
Headers

Content-Typeapplication/json

Path Parameters
  • bookingIdREQUIREDinteger
Body Parameters
  • statusOptionalno type

    Allowed values:newpendingconfirmedsi_submitteddraft_waybill_receiveddraft_waybill_confirmedevgm_submittedreleasepaymentfinishedcompletereject

  • ready_to_loadOptionalstring
  • partiesOptionalobject

    List of parties involved in the booking. The object may include up to three party objects: shipper, consignee, and notify (used for Shipping Instructions). Each party object contains the contact and company details required for the booking documentation.

  • productOptionalobject

    Product and cargo details used for creating the booking.

Response example
Booking data, retrieved after update
1{
2  "id": 0,
3  "shipment_id": 0,
4  "shipping_type": "string",
5  "created_at": "2026-08-24T14:15:22Z",
6  "ready_to_load": "2026-08-24T14:15:22Z",
7  "origin": {
8    "id": 0,
9    "name": "string",
10    "short_name": "string",
11    "country": "string",
12    "latitude": "string",
13    "longitude": "string",
14    "point_type": "string"
15  },
16  "destination": {
17    "id": 0,
18    "name": "string",
19    "short_name": "string",
20    "country": "string",
21    "latitude": "string",
22    "longitude": "string",
23    "point_type": "string"
24  },
25  "status": {
26    "id": 0,
27    "code": "string",
28    "name": "string",
29    "description": "string",
30    "global_name": "string",
31    "global_code": "string"
32  },
33  "parties": {
34    "shipper": {
35      "first_name": "string",
36      "last_name": "string",
37      "email": "string",
38      "phone": "string",
39      "company_name": "string",
40      "country": "string",
41      "city": "string",
42      "address": "string",
43      "zip_code": "string",
44      "vat_number": 0,
45      "freight_forwarder_reference": "string"
46    },
47    "consignee": {
48      "first_name": "string",
49      "last_name": "string",
50      "email": "string",
51      "phone": "string",
52      "company_name": "string",
53      "country": "string",
54      "city": "string",
55      "address": "string",
56      "zip_code": "string",
57      "vat_number": 0,
58      "freight_forwarder_reference": "string"
59    },
60    "notify": {
61      "first_name": "string",
62      "last_name": "string",
63      "email": "string",
64      "phone": "string",
65      "company_name": "string",
66      "country": "string",
67      "city": "string",
68      "address": "string",
69      "zip_code": "string",
70      "vat_number": 0,
71      "freight_forwarder_reference": "string"
72    }
73  },
74  "particulars": [
75    {
76      "id": 0,
77      "container": {},
78      "seal_number": "string",
79      "package_type_id": 0,
80      "package_quantity": 0,
81      "net_weight": 0,
82      "gross_weight": 0,
83      "gross_volume": 0,
84      "imo_class": "string",
85      "cargo_description": "string"
86    }
87  ],
88  "evgm": {
89    "id": 0,
90    "approval_signature": "string",
91    "approval_date": "2026-08-24",
92    "notify_email": "[email protected]",
93    "items": [
94      {
95        "container": {}
96      }
97    ]
98  }
99}

Receive booking webhooks

post/yourdomain.com/webhooks/searates.php

Outgoing webhook sent by SeaRates to your server

Handles multiple booking events:

  • booking.created - New booking created via API
  • booking.updated - Booking status or data changed
  • booking.status_changed - Booking status changed

HTTP Headers (Sent by SeaRates)

Your endpoint will receive the following headers with every webhook request:

X-Webhook-Signature (Required)

  • Type: string
  • Description: HMAC SHA256 signature of request body using your webhook_secret. Must be verified before processing.
  • Example: a3f2b1c5d8e7f4a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5

X-Webhook-Event (Required)

  • Type: string
  • Description: Event type that triggered this webhook.
  • Possible values: booking.created, booking.updated, booking.status_changed
  • Example: booking.created

X-Webhook-ID (Required)

  • Type: string (UUID format)
  • Description: Unique webhook delivery ID. Use for idempotency to prevent duplicate processing.
  • Example: 550e8400-e29b-41d4-a716-446655440000

X-Webhook-Timestamp (Required)

  • Type: integer (Unix timestamp)
  • Description: Unix timestamp when webhook was sent. Reject if older than 5 minutes to prevent replay attacks.
  • Example: 1703001600

Content-Type (Required)

  • Type: string
  • Value: Always application/json

⚠️ Security Note:
Always verify X-Webhook-Signature using hash_equals() before processing the payload. Never trust unverified webhooks.


Setup Instructions

  1. Contact your SeaRates manager to:

    • Configure your webhook_url (e.g., https://your-domain.com/webhooks/searates.php)
    • Receive your unique webhook_secret
  2. Store webhook_secret securely:

Option A: Environment Variable (Recommended)

# .env file
SEARATES_WEBHOOK_SECRET=your_secret_from_manager
// Load in your code
$secret = getenv('SEARATES_WEBHOOK_SECRET');
// or with Dotenv library:
$secret = $_ENV['SEARATES_WEBHOOK_SECRET'];

Option B: Configuration File

// config/webhooks.php (outside public directory)
return [
    'searates' => [
        'secret' => 'your_secret_from_manager',
        'url' => 'https://your-domain.com/webhooks/searates.php'
    ]
];

⚠️ Never hardcode webhook_secret in version control!

  1. Create processed_webhooks table (see SQL below)

  2. Implement signature verification (see PHP example)

  3. Return HTTP 200/201/204 within 5 seconds


Your Endpoint Requirements

  • Accept POST requests with Content-Type: application/json
  • Verify X-Webhook-Signature header (HMAC SHA256)
  • Check X-Webhook-Timestamp (reject if older than 5 minutes)
  • Handle duplicate deliveries using X-Webhook-ID
  • Return HTTP 200/201/204 within 5 seconds
  • For errors return HTTP 500 (SeaRates will retry)
  • Use HTTPS for your webhook endpoint

Database Schema (Required)

Create this table to prevent duplicate webhook processing:

CREATE TABLE processed_webhooks (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    webhook_id VARCHAR(36) UNIQUE NOT NULL COMMENT 'X-Webhook-ID from header',
    event_type VARCHAR(50) NOT NULL COMMENT 'Event name (booking.created, etc.)',
    processed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'Processing timestamp',
    INDEX idx_webhook_id (webhook_id),
    INDEX idx_processed_at (processed_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Optional: Clean up old records (run daily via cron)
DELETE FROM processed_webhooks
WHERE processed_at < DATE_SUB(NOW(), INTERVAL 30 DAY);

Complete PHP Implementation

<?php
/**
 * Webhook receiver for SeaRates Booking API
 * File: public/webhooks/searates.php
 */

// 1. Get request data and headers
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$event = $_SERVER['HTTP_X_WEBHOOK_EVENT'] ?? '';
$webhookId = $_SERVER['HTTP_X_WEBHOOK_ID'] ?? '';
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';

// 2. Get webhook secret from environment
$secret = getenv('SEARATES_WEBHOOK_SECRET');
// Or from config file:
// $config = require __DIR__ . '/../../config/webhooks.php';
// $secret = $config['searates']['secret'];

if (!$secret) {
    http_response_code(500);
    exit(json_encode(['success' => false, 'error' => 'Webhook secret not configured']));
}

// 3. Verify signature (REQUIRED)
if (empty($signature)) {
    http_response_code(401);
    exit(json_encode(['success' => false, 'error' => 'Missing X-Webhook-Signature header']));
}

$expectedSignature = hash_hmac('sha256', $payload, $secret);

if (!hash_equals($expectedSignature, $signature)) {
    http_response_code(401);
    exit(json_encode(['success' => false, 'error' => 'Invalid signature']));
}

// 4. Check timestamp (protect against replay attacks)
$currentTimestamp = time();
$webhookTimestamp = (int)$timestamp;
$webhookAge = $currentTimestamp - $webhookTimestamp;

if ($webhookAge > 300) { // 5 minutes
    http_response_code(400);
    exit(json_encode(['success' => false, 'error' => 'Webhook too old']));
}

// 5. Database connection
$pdo = new PDO('mysql:host=localhost;dbname=yourdb', 'user', 'pass');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// 6. Check for duplicates (idempotency)
$stmt = $pdo->prepare('SELECT id FROM processed_webhooks WHERE webhook_id = ?');
$stmt->execute([$webhookId]);

if ($stmt->fetch()) {
    // Already processed - return success to prevent retry
    http_response_code(200);
    exit(json_encode(['success' => true, 'duplicate' => true]));
}

// 7. Parse JSON
$data = json_decode($payload, true);

if (json_last_error() !== JSON_ERROR_NONE) {
    http_response_code(400);
    exit(json_encode(['success' => false, 'error' => 'Invalid JSON']));
}

// 8. Process event
try {
    switch ($data['event']) {
        case 'booking.created':
            handleBookingCreated($pdo, $data['data']);
            break;

        case 'booking.updated':
            handleBookingUpdated($pdo, $data['data']);
            break;

        case 'booking.status_changed':
            handleBookingStatusChanged($pdo, $data['data']);
            break;

        default:
            http_response_code(200);
            exit(json_encode(['success' => true, 'processed' => false]));
    }

    // Mark as processed
    $stmt = $pdo->prepare(
        'INSERT INTO processed_webhooks (webhook_id, event_type) VALUES (?, ?)'
    );
    $stmt->execute([$webhookId, $event]);

    http_response_code(200);
    echo json_encode(['success' => true, 'message' => 'Webhook processed successfully']);

} catch (Exception $e) {
    // Log error
    error_log('Webhook error: ' . $e->getMessage());

    // Return 500 so SeaRates retries
    http_response_code(500);
    exit(json_encode(['success' => false, 'error' => 'Internal server error']));
}

/**
 * Event Handlers
 */
function handleBookingCreated(PDO $pdo, array $booking): void
{
    $stmt = $pdo->prepare(
        'INSERT INTO bookings (searates_id, type, status, data, created_at)
         VALUES (?, ?, ?, ?, NOW())'
    );
    $stmt->execute([
        $booking['id'],
        $booking['type'] ?? null,
        $booking['status']['name'] ?? null,
        json_encode($booking),
    ]);
}

function handleBookingUpdated(PDO $pdo, array $booking): void
{
    $stmt = $pdo->prepare(
        'UPDATE bookings
         SET status = ?, data = ?, updated_at = NOW()
         WHERE searates_id = ?'
    );
    $stmt->execute([
        $booking['status']['name'] ?? null,
        json_encode($booking),
        $booking['id'],
    ]);
}

function handleBookingStatusChanged(PDO $pdo, array $booking): void
{
    $oldStatus = $booking['old_status'] ?? null;
    $newStatus = $booking['status']['name'] ?? null;

    error_log("Booking {$booking['id']}: $oldStatus -> $newStatus");

    // Update status
    handleBookingUpdated($pdo, $booking);
}

Retry Policy

  • Failed deliveries (HTTP 5xx or timeout) are automatically retried 3 times
  • Retry schedule: 30 seconds1 minute5 minutes
  • After 3 failed attempts, webhook delivery is abandoned
  • Your endpoint must respond within 5 seconds

Security Best Practices

  • Always verify X-Webhook-Signature before processing
  • Use hash_equals() to prevent timing attacks
  • Check X-Webhook-Timestamp (max age: 5 minutes)
  • Implement idempotency using X-Webhook-ID
  • Store webhook_secret in environment variable or config file
  • Never commit webhook_secret to version control
  • Never log or expose webhook_secret
  • Return HTTP 200 within 5 seconds
  • Use HTTPS for your webhook endpoint
Headers

Content-Typeapplication/json

Body Parameters
  • eventREQUIREDstring

    Type of event that triggered the webhook

    Allowed values:booking.createdbooking.updatedbooking.status_changed

    Example:booking.created

  • timestampREQUIREDstring

    ISO 8601 timestamp when the event occurred

    Example:2026-01-15T10:30:00Z

  • dataREQUIREDobject

    Complete booking object at the time of the event

Response example
Webhook received and processed successfully