DepartCart API Reference

Overview

DepartCart provides secure, white-label ancillary services for airlines and travel partners. Partners authenticate with an API key and secret to obtain a short-lived bearer token, then call the API with the raw booking details (PNR and passenger last name). DepartCart performs all encryption server-side, so partners never handle encryption keys.

Table of Contents

  1. Authentication & Bearer Tokens
  2. Generate Seatmap Deeplink
  3. Generate All Deeplinks
  4. Seatmap Display
  5. GTM Integration
  6. Webhook Notifications
  7. Error Handling
  8. Implementation Guide

Authentication & Bearer Tokens

All API calls are authenticated with a short-lived bearer token. You exchange your brand's API key and secret (issued during onboarding) for a token, then send it in the Authorization header on every request.

Note: Your API key and secret are provided during onboarding and differ from the examples shown here. Encryption is handled entirely server-side β€” partners never receive or use encryption keys, passwords, or salts.

Step 1 β€” Request a bearer token

Endpoint: POST /generate-bearer-token

Request Body:

{
  "api_key": "YOUR_API_KEY",
  "api_secret": "YOUR_API_SECRET",
  "duration_hours": 24
}

duration_hours is optional and defaults to 24.

Response:

{
  "success": true,
  "bearer_token": "your_brand:1718790000:1718876400:Ab12Cd...:9f8e7d...",
  "token_type": "Bearer",
  "expires_in": 86400,
  "expires_at": 1718876400,
  "brand": "your_brand"
}

Step 2 β€” Authenticate requests

Send the token in the Authorization header:

Authorization: Bearer your_brand:1718790000:1718876400:Ab12Cd...:9f8e7d...

Tokens expire after duration_hours; a 401 response indicates an invalid or expired token. Cache and reuse a token until it is close to expiry rather than requesting a new one on every call.


Create an encrypted seatmap URL for a booking. Send the raw pnr and last_name; DepartCart encrypts them server-side and returns the deeplink. The brand is taken from your bearer token.

Endpoint: POST /api/generate-encrypted-seatmap-url

Headers:

Authorization: Bearer <token>
Content-Type: application/json

Request Body:

Field Type Required Description
pnr string Yes Booking reference / PNR (e.g. ABC123).
last_name string Yes Passenger last name (e.g. SMITH).
source string No Integration source identifier (e.g. api, app). Defaults to cart.
{
  "pnr": "ABC123",
  "last_name": "SMITH",
  "source": "api"
}

Response:

{
  "success": true,
  "encrypted_id": "Z0FBQUFBQm9VQTE2...",
  "seatmap_url": "/seatmap?id=Z0FBQUFBQm9VQTE2...&source=api",
  "source": "api"
}

seatmap_url is a path on the DepartCart domain. Prepend https://departcart.com to build the full customer-facing deeplink:

https://departcart.com/seatmap?id=Z0FBQUFBQm9VQTE2...&source=api

Example Usage:

import requests

BASE_URL = "https://departcart.com"

# 1) Exchange API credentials for a bearer token
auth = requests.post(f"{BASE_URL}/generate-bearer-token", json={
    "api_key": "YOUR_API_KEY",
    "api_secret": "YOUR_API_SECRET",
}).json()
token = auth["bearer_token"]

# 2) Generate the seatmap deeplink for a booking
resp = requests.post(
    f"{BASE_URL}/api/generate-encrypted-seatmap-url",
    headers={"Authorization": f"Bearer {token}"},
    json={"pnr": "ABC123", "last_name": "SMITH", "source": "api"},
).json()

seatmap_deeplink = f"{BASE_URL}{resp['seatmap_url']}"
print(seatmap_deeplink)
const BASE_URL = 'https://departcart.com';

// 1) Exchange API credentials for a bearer token
const auth = await fetch(`${BASE_URL}/generate-bearer-token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        api_key: 'YOUR_API_KEY',
        api_secret: 'YOUR_API_SECRET',
    }),
}).then(r => r.json());

// 2) Generate the seatmap deeplink for a booking
const result = await fetch(`${BASE_URL}/api/generate-encrypted-seatmap-url`, {
    method: 'POST',
    headers: {
        'Authorization': `Bearer ${auth.bearer_token}`,
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({ pnr: 'ABC123', last_name: 'SMITH', source: 'api' }),
}).then(r => r.json());

const seatmapDeeplink = `${BASE_URL}${result.seatmap_url}`;
console.log(seatmapDeeplink);
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;

const string baseUrl = "https://departcart.com";
var client = new HttpClient();

// 1) Exchange API credentials for a bearer token
var authResp = await client.PostAsJsonAsync($"{baseUrl}/generate-bearer-token", new
{
    api_key = "YOUR_API_KEY",
    api_secret = "YOUR_API_SECRET",
});
var auth = await authResp.Content.ReadFromJsonAsync<Dictionary<string, object>>();
string token = auth["bearer_token"].ToString();

// 2) Generate the seatmap deeplink for a booking
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var resp = await client.PostAsJsonAsync($"{baseUrl}/api/generate-encrypted-seatmap-url", new
{
    pnr = "ABC123",
    last_name = "SMITH",
    source = "api",
});
var result = await resp.Content.ReadFromJsonAsync<Dictionary<string, object>>();
string seatmapDeeplink = $"{baseUrl}{result["seatmap_url"]}";
require 'net/http'
require 'uri'
require 'json'

BASE_URL = 'https://departcart.com'

def post_json(path, body, token = nil)
  uri = URI("#{BASE_URL}#{path}")
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  req = Net::HTTP::Post.new(uri)
  req['Content-Type'] = 'application/json'
  req['Authorization'] = "Bearer #{token}" if token
  req.body = body.to_json
  JSON.parse(http.request(req).body)
end

# 1) Exchange API credentials for a bearer token
auth = post_json('/generate-bearer-token', {
  api_key: 'YOUR_API_KEY',
  api_secret: 'YOUR_API_SECRET'
})

# 2) Generate the seatmap deeplink for a booking
result = post_json('/api/generate-encrypted-seatmap-url',
  { pnr: 'ABC123', last_name: 'SMITH', source: 'api' },
  auth['bearer_token'])

seatmap_deeplink = "#{BASE_URL}#{result['seatmap_url']}"
puts seatmap_deeplink

Generate the seatmap, cart, and checkout deeplinks for a booking in a single call. As with the seatmap endpoint, encryption is performed server-side and the brand is taken from your bearer token β€” the difference is that this endpoint returns all three deeplinks as absolute URLs.

Endpoint: POST /generate-encrypted-url

Headers:

Authorization: Bearer <token>
Content-Type: application/json

Request Body:

Field Type Required Description
pnr string Yes Booking reference / PNR.
last_name string Yes Any passenger's last name from the booking.
source string No Integration source identifier. Defaults to cart.
{
  "pnr": "ABC123",
  "last_name": "SMITH",
  "source": "cart"
}

Response:

{
  "success": true,
  "encrypted_id": "Z0FBQUFBQm9VQTE2...",
  "seatmap_url": "https://departcart.com/seatmap?id=Z0FBQUFBQm9VQTE2...&source=cart",
  "cart_url": "https://departcart.com/cart?id=Z0FBQUFBQm9VQTE2...",
  "checkout_url": "https://departcart.com/checkout?id=Z0FBQUFBQm9VQTE2...",
  "source": "cart"
}

Use seatmap_url to drop the customer directly into seat selection, cart_url for the full ancillary cart, or checkout_url to jump straight to payment. These are absolute URLs and can be used as-is.


Seatmap Display

Endpoint: GET /seatmap

Parameters:

Example:

https://departcart.com/seatmap?id=Z0FBQUFBQm9VQTE2...&source=api

GTM Integration

Lightweight Integration

For a quick, low-maintenance integration, use Google Tag Manager to inject seat selection panels into your confirmation or account pages.

GTM Code Template:

<script>
(function() {
    // Ultra-minimal GTM injectable - just loads the script, no logic
    var script = document.createElement('script');
    script.src = 'https://departcart.com/static/loaders-dist/seat_selection_loader.js';
    script.setAttribute('data-gtm-source', 'true');
    document.head.appendChild(script);
})();
</script>

Benefits:

Setup Steps:

  1. Add the GTM code above to your tag manager
  2. Configure trigger for confirmation/account pages
  3. Ensure your pages have identifiable booking elements
  4. Test with sample bookings

For detailed GTM integration instructions, see: GTM Integration Guide


Webhook Notifications

Real-Time Transaction Updates

DepartCart sends webhook notifications for all transaction events to keep partners updated in real-time.

Webhook URL Setup: Configure your webhook endpoint during onboarding.

Event Types

Webhook Payload

{
  "event_type": "transaction.completed",
  "event_id": "evt_1234567890",
  "timestamp": "2025-06-16T10:30:00Z",
  "brand": "your_brand_name",
  "transaction": {
    "id": "txn_abc123def456",
    "pnr": "ABC123",
    "status": "completed",
    "total_amount": 89.98,
    "currency": "USD",
    "payment": {
      "method": "stripe",
      "gateway_transaction_id": "pi_1234567890",
      "amount_charged": 89.98,
      "processing_fee": 2.70,
      "net_amount": 87.28
    },
    "passenger": {
      "first_name": "John",
      "last_name": "SMITH",
      "email": "john.smith@example.com"
    },
    "flight": {
      "airline_code": "AA",
      "flight_number": "1234",
      "departure_date": "2025-06-20",
      "departure_airport": "JFK",
      "arrival_airport": "LAX",
      "segment_id": "AA1234-20250620-JFKLAX"
    },
    "ancillaries": [
      {
        "type": "seat",
        "description": "Premium Economy Seat 12A",
        "seat_number": "12A",
        "price": 45.00,
        "cost": 15.00,
        "margin": 30.00
      },
      {
        "type": "baggage",
        "description": "Extra Baggage 23kg",
        "weight": "23kg",
        "price": 44.98,
        "cost": 25.00,
        "margin": 19.98
      }
    ],
    "fulfillment": {
      "seat_confirmation": "CONFIRMED",
      "baggage_confirmation": "CONFIRMED",
      "gds_record_locator": "ABC123",
      "updated_at": "2025-06-16T10:29:45Z"
    }
  }
}

Webhook Implementation Examples

from flask import Flask, request, jsonify
import hmac
import hashlib

app = Flask(__name__)

@app.route('/webhook/departcart', methods=['POST'])
def handle_departcart_webhook():
    # Verify webhook signature (recommended)
    signature = request.headers.get('X-DepartCart-Signature')
    payload = request.get_data()

    if not verify_webhook_signature(payload, signature):
        return jsonify({'error': 'Invalid signature'}), 401

    # Process webhook data
    webhook_data = request.get_json()
    event_type = webhook_data['event_type']
    transaction = webhook_data['transaction']

    if event_type == 'transaction.completed':
        # Update your records
        update_booking_ancillaries(
            pnr=transaction['pnr'],
            ancillaries=transaction['ancillaries'],
            payment_info=transaction['payment']
        )

        # Send confirmation email
        send_purchase_confirmation(
            passenger=transaction['passenger'],
            flight=transaction['flight'],
            ancillaries=transaction['ancillaries']
        )

    elif event_type == 'transaction.refunded':
        # Process refund
        process_refund(
            transaction_id=transaction['id'],
            refund_amount=transaction['payment']['amount_charged']
        )

    return jsonify({'status': 'received'}), 200

def verify_webhook_signature(payload, signature):
    # Verify using your webhook secret
    webhook_secret = "your_webhook_secret_key"
    expected_signature = hmac.new(
        webhook_secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(f"sha256={expected_signature}", signature)
using Microsoft.AspNetCore.Mvc;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

[ApiController]
[Route("webhook")]
public class WebhookController : ControllerBase
{
    [HttpPost("departcart")]
    public async Task<IActionResult> HandleDepartCartWebhook()
    {
        // Read webhook payload
        using var reader = new StreamReader(Request.Body);
        var payload = await reader.ReadToEndAsync();

        // Verify signature
        var signature = Request.Headers["X-DepartCart-Signature"].FirstOrDefault();
        if (!VerifyWebhookSignature(payload, signature))
        {
            return Unauthorized("Invalid signature");
        }

        // Parse webhook data
        var webhookData = JsonSerializer.Deserialize<WebhookPayload>(payload);

        switch (webhookData.event_type)
        {
            case "transaction.completed":
                await HandleTransactionCompleted(webhookData.transaction);
                break;

            case "transaction.refunded":
                await HandleTransactionRefunded(webhookData.transaction);
                break;
        }

        return Ok(new { status = "received" });
    }

    private bool VerifyWebhookSignature(string payload, string signature)
    {
        var webhookSecret = "your_webhook_secret_key";
        var keyBytes = Encoding.UTF8.GetBytes(webhookSecret);
        var payloadBytes = Encoding.UTF8.GetBytes(payload);

        using var hmac = new HMACSHA256(keyBytes);
        var computedHash = hmac.ComputeHash(payloadBytes);
        var expectedSignature = $"sha256={Convert.ToHexString(computedHash).ToLower()}";

        return string.Equals(expectedSignature, signature, StringComparison.OrdinalIgnoreCase);
    }
}
const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.raw({ type: 'application/json' }));

app.post('/webhook/departcart', (req, res) => {
    const signature = req.headers['x-departcart-signature'];
    const payload = req.body;

    // Verify webhook signature
    if (!verifyWebhookSignature(payload, signature)) {
        return res.status(401).json({ error: 'Invalid signature' });
    }

    // Parse webhook data
    const webhookData = JSON.parse(payload);
    const { event_type, transaction } = webhookData;

    switch (event_type) {
        case 'transaction.completed':
            handleTransactionCompleted(transaction);
            break;

        case 'transaction.refunded':
            handleTransactionRefunded(transaction);
            break;
    }

    res.json({ status: 'received' });
});

function verifyWebhookSignature(payload, signature) {
    const webhookSecret = 'your_webhook_secret_key';
    const expectedSignature = crypto
        .createHmac('sha256', webhookSecret)
        .update(payload)
        .digest('hex');

    return crypto.timingSafeEqual(
        Buffer.from(`sha256=${expectedSignature}`),
        Buffer.from(signature)
    );
}

async function handleTransactionCompleted(transaction) {
    // Update booking records
    await updateBookingAncillaries(transaction);

    // Send confirmation email
    await sendPurchaseConfirmation(transaction);
}
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import javax.servlet.http.HttpServletRequest;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;

@RestController
@RequestMapping("/webhook")
public class WebhookController {

    private static final String WEBHOOK_SECRET = "your_webhook_secret_key";

    @PostMapping("/departcart")
    public ResponseEntity<Map<String, String>> handleDepartCartWebhook(
            HttpServletRequest request, @RequestBody String payload) {

        try {
            // Verify signature
            String signature = request.getHeader("X-DepartCart-Signature");
            if (!verifyWebhookSignature(payload, signature)) {
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                    .body(Map.of("error", "Invalid signature"));
            }

            // Parse webhook data
            ObjectMapper mapper = new ObjectMapper();
            Map<String, Object> webhookData = mapper.readValue(payload, Map.class);
            String eventType = (String) webhookData.get("event_type");
            Map<String, Object> transaction = (Map<String, Object>) webhookData.get("transaction");

            switch (eventType) {
                case "transaction.completed":
                    handleTransactionCompleted(transaction);
                    break;

                case "transaction.refunded":
                    handleTransactionRefunded(transaction);
                    break;
            }

            return ResponseEntity.ok(Map.of("status", "received"));

        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(Map.of("error", "Processing error"));
        }
    }

    private boolean verifyWebhookSignature(String payload, String signature) 
            throws NoSuchAlgorithmException, InvalidKeyException {

        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKey = new SecretKeySpec(WEBHOOK_SECRET.getBytes(), "HmacSHA256");
        mac.init(secretKey);

        byte[] hash = mac.doFinal(payload.getBytes());
        StringBuilder expectedSignature = new StringBuilder("sha256=");
        for (byte b : hash) {
            expectedSignature.append(String.format("%02x", b));
        }

        return expectedSignature.toString().equals(signature);
    }

    private void handleTransactionCompleted(Map<String, Object> transaction) {
        // Update booking records and send confirmation email
        System.out.println("Transaction completed: " + transaction.get("id"));
    }

    private void handleTransactionRefunded(Map<String, Object> transaction) {
        // Process refund
        System.out.println("Transaction refunded: " + transaction.get("id"));
    }
}
require 'sinatra'
require 'json'
require 'openssl'

class WebhookController < Sinatra::Base
  WEBHOOK_SECRET = 'your_webhook_secret_key'

  post '/webhook/departcart' do
    # Read payload
    request.body.rewind
    payload = request.body.read

    # Verify signature
    signature = request.env['HTTP_X_DEPARTCART_SIGNATURE']
    unless verify_webhook_signature(payload, signature)
      status 401
      return { error: 'Invalid signature' }.to_json
    end

    # Parse webhook data
    webhook_data = JSON.parse(payload)
    event_type = webhook_data['event_type']
    transaction = webhook_data['transaction']

    case event_type
    when 'transaction.completed'
      handle_transaction_completed(transaction)
    when 'transaction.refunded'
      handle_transaction_refunded(transaction)
    end

    content_type :json
    { status: 'received' }.to_json
  end

  private

  def verify_webhook_signature(payload, signature)
    expected_signature = "sha256=#{OpenSSL::HMAC.hexdigest('SHA256', WEBHOOK_SECRET, payload)}"
    Rack::Utils.secure_compare(expected_signature, signature)
  end

  def handle_transaction_completed(transaction)
    # Update booking records and send confirmation email
    puts "Transaction completed: #{transaction['id']}"
  end

  def handle_transaction_refunded(transaction)
    # Process refund
    puts "Transaction refunded: #{transaction['id']}"
  end
end

Error Handling

HTTP Status Codes

Error Response Format

{
    "success": false,
    "error": {
        "code": "INVALID_PNR",
        "message": "The provided PNR could not be found or is invalid",
        "details": {
            "pnr": "ABC123",
            "brand": "your_brand_name"
        }
    }
}

Common Error Codes


Implementation Guide

πŸš€ Quick Start Checklist

Complete these steps to get your DepartCart integration up and running quickly.


πŸ“‹ Phase 1: Onboarding Requirements

Essential setup items needed before development begins


βš™οΈ Phase 2: Integration Options

Choose your preferred integration approach


πŸ§ͺ Phase 3: Testing

Validate your integration before going live


🎯 Phase 4: Go Live

Final steps for production deployment


⏱️ Estimated Timeline: 4-8 weeks total

Integration Timeline

Phase Duration Activities
Setup 1-2 weeks Onboarding, credentials, branding
Development 2-4 weeks Implement encryption, integrate APIs
Testing 1-2 weeks End-to-end testing, UAT
Launch 1 week Production deployment, monitoring

For detailed implementation steps, see: Partner Onboarding Guide


Support & Resources

Support Contacts

Testing Tools


This documentation is updated regularly. For the latest version, visit: https://departcart.com/docs