Documentation

DepartCart Integration Guide

Quick Integration Options

Option 1: Google Tag Manager (Fastest - 1 day)

Perfect for airlines that want seat selection on confirmation pages with minimal development effort.

What you need:

  • Access to Google Tag Manager
  • Booking confirmation pages with identifiable PNR/passenger data

Setup: 1. Add this code to GTM:

<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>
  1. Configure trigger for confirmation/account pages
  2. Test with sample bookings

Result: Automatic seat selection panels appear on confirmation pages, securely encrypted URLs, fully responsive design.


⭐ This is the recommended approach. Generate seatmap deeplinks through our API using a bearer token. All encryption happens entirely on our servers. You send the raw pnr and last_name, and we return a ready-to-use deeplink. You never handle any encryption keys or secrets beyond your API credentials.

The flow is two steps:

  1. Get a bearer token from POST /generate-bearer-token using your api_key and api_secret.
  2. Generate the deeplink from POST /api/generate-encrypted-seatmap-url, authenticating with the bearer token. The brand is derived from the token, so it is never sent in the request body.

Benefits: - No cryptography to implement: Encryption is handled on our servers; you only send raw passenger data. - Simple credentials: A single api_key / api_secret pair, with short-lived bearer tokens. - Brand inferred from token: No brand field to manage in each request. - Token caching: Reuse a bearer token across many deeplink requests until it expires.

Keep your api_secret server-side. Request bearer tokens from your backend, never from browser or client code.

1. Generate a Bearer Token

Endpoint: POST https://departcart.com/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:...:...:...:...",
  "token_type": "Bearer",
  "expires_in": 86400,
  "expires_at": 1718876400,
  "brand": "your_brand"
}

Cache the bearer_token and reuse it until expires_at.

Endpoint: POST https://departcart.com/api/generate-encrypted-seatmap-url

Headers:

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

Request body:

{
  "pnr": "ABC123",
  "last_name": "SMITH",
  "source": "api"
}

source is optional and defaults to "cart". Do not send a brand field. The brand is derived from the bearer token.

Response:

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

seatmap_url is a path on the DepartCart domain. Build the customer-facing deeplink by prefixing the base URL:

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

3. Code Examples

import requests

BASE_URL = "https://departcart.com"

def get_bearer_token(api_key, api_secret, duration_hours=24):
    """Exchange API credentials for a short-lived bearer token."""
    response = requests.post(f"{BASE_URL}/generate-bearer-token", json={
        "api_key": api_key,
        "api_secret": api_secret,
        "duration_hours": duration_hours
    })
    response.raise_for_status()
    return response.json()["bearer_token"]

def generate_seat_selection_url(bearer_token, pnr, last_name, source="api"):
    """Generate a seatmap deeplink. Encryption happens server-side."""
    response = requests.post(
        f"{BASE_URL}/api/generate-encrypted-seatmap-url",
        headers={
            "Authorization": f"Bearer {bearer_token}",
            "Content-Type": "application/json"
        },
        json={
            "pnr": pnr,
            "last_name": last_name,
            "source": source
        }
    )
    if response.status_code == 200:
        # seatmap_url is a path; prefix with the base URL for the deeplink
        return BASE_URL + response.json()["seatmap_url"]
    return None  # Handle error

# Usage in your booking flow (keep api_secret server-side)
token = get_bearer_token("YOUR_API_KEY", "YOUR_API_SECRET")
seatmap_url = generate_seat_selection_url(token, "ABC123", "SMITH")
if seatmap_url:
    print(f"Seat selection: {seatmap_url}")
const BASE_URL = 'https://departcart.com';

async function getBearerToken(apiKey, apiSecret, durationHours = 24) {
    // Call this from your backend so the api_secret stays server-side
    const response = await fetch(`${BASE_URL}/generate-bearer-token`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            api_key: apiKey,
            api_secret: apiSecret,
            duration_hours: durationHours
        })
    });
    if (!response.ok) throw new Error('Failed to get bearer token');
    const data = await response.json();
    return data.bearer_token;
}

async function generateSeatSelectionUrl(bearerToken, pnr, lastName, source = 'api') {
    try {
        const response = await fetch(`${BASE_URL}/api/generate-encrypted-seatmap-url`, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${bearerToken}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ pnr, last_name: lastName, source })
        });

        if (response.ok) {
            const data = await response.json();
            // seatmap_url is a path; prefix with the base URL for the deeplink
            return BASE_URL + data.seatmap_url;
        }
        return null; // Handle error
    } catch (error) {
        console.error('Error generating seat selection URL:', error);
        return null;
    }
}

// Usage in your booking flow
const token = await getBearerToken('YOUR_API_KEY', 'YOUR_API_SECRET');
const seatmapUrl = await generateSeatSelectionUrl(token, 'ABC123', 'SMITH');
if (seatmapUrl) {
    console.log(`Seat selection: ${seatmapUrl}`);
}
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

public class DepartCartClient
{
    private const string BaseUrl = "https://departcart.com";
    private readonly HttpClient _client = new();

    public async Task<string> GetBearerTokenAsync(string apiKey, string apiSecret, int durationHours = 24)
    {
        // Call this from your backend so the api_secret stays server-side
        var payload = new { api_key = apiKey, api_secret = apiSecret, duration_hours = durationHours };
        var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");

        var response = await _client.PostAsync($"{BaseUrl}/generate-bearer-token", content);
        response.EnsureSuccessStatusCode();

        var result = JsonSerializer.Deserialize<TokenResponse>(await response.Content.ReadAsStringAsync());
        return result.bearer_token;
    }

    public async Task<string> GenerateSeatSelectionUrlAsync(string bearerToken, string pnr, string lastName, string source = "api")
    {
        var payload = new { pnr, last_name = lastName, source };
        var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/api/generate-encrypted-seatmap-url")
        {
            Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
        };
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);

        var response = await _client.SendAsync(request);
        if (response.IsSuccessStatusCode)
        {
            var result = JsonSerializer.Deserialize<SeatmapResponse>(await response.Content.ReadAsStringAsync());
            // seatmap_url is a path; prefix with the base URL for the deeplink
            return BaseUrl + result.seatmap_url;
        }
        return null; // Handle error
    }
}

public class TokenResponse { public string bearer_token { get; set; } }
public class SeatmapResponse { public string seatmap_url { get; set; } }

// Usage in your booking flow
var client = new DepartCartClient();
var token = await client.GetBearerTokenAsync("YOUR_API_KEY", "YOUR_API_SECRET");
var seatmapUrl = await client.GenerateSeatSelectionUrlAsync(token, "ABC123", "SMITH");
if (seatmapUrl != null)
{
    Console.WriteLine($"Seat selection: {seatmapUrl}");
}
require 'net/http'
require 'uri'
require 'json'

class DepartCartAPI
  BASE_URL = 'https://departcart.com'

  def self.get_bearer_token(api_key, api_secret, duration_hours = 24)
    # Call this from your backend so the api_secret stays server-side
    uri = URI("#{BASE_URL}/generate-bearer-token")
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true

    request = Net::HTTP::Post.new(uri)
    request['Content-Type'] = 'application/json'
    request.body = {
      api_key: api_key,
      api_secret: api_secret,
      duration_hours: duration_hours
    }.to_json

    response = http.request(request)
    raise 'Failed to get bearer token' unless response.code == '200'
    JSON.parse(response.body)['bearer_token']
  end

  def self.generate_seat_selection_url(bearer_token, pnr, last_name, source = 'api')
    uri = URI("#{BASE_URL}/api/generate-encrypted-seatmap-url")
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true

    request = Net::HTTP::Post.new(uri)
    request['Authorization'] = "Bearer #{bearer_token}"
    request['Content-Type'] = 'application/json'
    request.body = { pnr: pnr, last_name: last_name, source: source }.to_json

    response = http.request(request)
    if response.code == '200'
      # seatmap_url is a path; prefix with the base URL for the deeplink
      BASE_URL + JSON.parse(response.body)['seatmap_url']
    end
  end
end

# Usage in your booking flow
token = DepartCartAPI.get_bearer_token('YOUR_API_KEY', 'YOUR_API_SECRET')
seatmap_url = DepartCartAPI.generate_seat_selection_url(token, 'ABC123', 'SMITH')
puts "Seat selection: #{seatmap_url}" if seatmap_url

If you want more than the seatmap link, call POST https://departcart.com/generate-encrypted-url instead. It uses the same bearer-token auth and { "pnr", "last_name", "source" } body, but returns the seatmap, cart, and checkout deeplinks together as absolute URLs:

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

Use seatmap_url for seat selection, cart_url for the full ancillary cart, or checkout_url to jump straight to payment.

4. Add "Select Seats" Buttons

<!-- In your booking confirmation template -->
<div class="flight-summary">
    <h3>Flight AA1234 - JFK to LAX</h3>
    <p>June 20, 2025 at 8:00 AM</p>

    {% if seatmap_url %}
    <a href="{{ seatmap_url }}" class="btn btn-primary" target="_blank">
        🪑 Select Seats & Add-ons
    </a>
    {% endif %}
</div>

5. Handle Webhook Notifications

from flask import Flask, request, jsonify

@app.route('/webhook/departcart', methods=['POST'])
def handle_ancillary_purchase():
    """Receive real-time notifications when passengers buy ancillaries"""

    webhook_data = request.get_json()

    if webhook_data['event_type'] == 'transaction.completed':
        transaction = webhook_data['transaction']

        # Update your booking system
        update_passenger_ancillaries(
            pnr=transaction['pnr'],
            seats=transaction['ancillaries']
        )

        # Send confirmation email
        send_ancillary_confirmation_email(transaction)

    return jsonify({'status': 'received'})
using Microsoft.AspNetCore.Mvc;
using System.Text.Json;

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

        if (webhookData.event_type == "transaction.completed")
        {
            var transaction = webhookData.transaction;

            // Update your booking system
            await UpdatePassengerAncillaries(
                transaction.pnr,
                transaction.ancillaries
            );

            // Send confirmation email
            await SendAncillaryConfirmationEmail(transaction);
        }

        return Ok(new { status = "received" });
    }
}
const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhook/departcart', async (req, res) => {
    try {
        const webhookData = req.body;

        if (webhookData.event_type === 'transaction.completed') {
            const transaction = webhookData.transaction;

            // Update your booking system
            await updatePassengerAncillaries(
                transaction.pnr,
                transaction.ancillaries
            );

            // Send confirmation email
            await sendAncillaryConfirmationEmail(transaction);
        }

        res.json({ status: 'received' });
    } catch (error) {
        console.error('Webhook error:', error);
        res.status(500).json({ error: 'Internal server error' });
    }
});
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;

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

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

        try {
            ObjectMapper mapper = new ObjectMapper();
            Map<String, Object> webhookData = mapper.readValue(payload, Map.class);
            String eventType = (String) webhookData.get("event_type");

            if ("transaction.completed".equals(eventType)) {
                Map<String, Object> transaction = (Map<String, Object>) webhookData.get("transaction");

                // Update your booking system
                updatePassengerAncillaries(
                    (String) transaction.get("pnr"),
                    (java.util.List<?>) transaction.get("ancillaries")
                );

                // Send confirmation email
                sendAncillaryConfirmationEmail(transaction);
            }

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

        } catch (Exception e) {
            return ResponseEntity.status(500)
                .body(Map.of("error", "Internal server error"));
        }
    }

    private void updatePassengerAncillaries(String pnr, java.util.List<?> ancillaries) {
        // Implementation for updating passenger ancillaries
    }

    private void sendAncillaryConfirmationEmail(Map<String, Object> transaction) {
        // Implementation for sending confirmation email
    }
}
require 'sinatra'
require 'json'

class WebhookController < Sinatra::Base

  post '/webhook/departcart' do
    begin
      # Read payload
      request.body.rewind
      payload = request.body.read
      webhook_data = JSON.parse(payload)

      if webhook_data['event_type'] == 'transaction.completed'
        transaction = webhook_data['transaction']

        # Update your booking system
        update_passenger_ancillaries(
          transaction['pnr'],
          transaction['ancillaries']
        )

        # Send confirmation email
        send_ancillary_confirmation_email(transaction)
      end

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

    rescue => e
      status 500
      { error: 'Internal server error' }.to_json
    end
  end

  private

  def update_passenger_ancillaries(pnr, ancillaries)
    # Implementation for updating passenger ancillaries
  end

  def send_ancillary_confirmation_email(transaction)
    # Implementation for sending confirmation email
  end
end

Integration Comparison

Feature GTM API Integration ⭐
Setup Time 1 day 1-2 days
Development Minimal Low
Customization Limited High
Security High High (server-side encryption)
Performance Good Good
Credentials None API key + secret
Maintenance None Low

What We Provide During Onboarding

Brand Configuration

  • API key and secret for bearer-token authentication
  • Brand-specific color themes and styling
  • Custom domain options (optional)

GDS Integration

  • Sabre/Amadeus connection setup
  • Seat map data configuration
  • Pricing and availability rules

Payment Processing

  • Stripe/Adyen gateway configuration
  • Commission/markup structure
  • Multi-currency support

Webhook Setup

  • Real-time transaction notifications
  • Custom endpoint configuration
  • Event filtering options

Testing Tools

  • Sandbox environment access
  • Deeplink generation tools
  • Bearer-token validation utilities

Working out the opportunity

We do not publish conversion rates or per-transaction figures, and it is worth being sceptical of anyone who does. Ancillary performance varies so much by route mix, cabin, lead time and audience that another operator's numbers tell you very little about your own traffic.

What we can give you is the arithmetic, run on figures you already hold. Ancillary revenue works an example through end to end, and ancillary revenue benchmarks sets out why the published benchmarks are not comparable with each other, let alone with you.

Implementation time is the one figure we will commit to: one to fourteen days, depending on which of the three integration routes you take.


Getting Started

  1. Contact Integration Team: integration@departcart.com
  2. Schedule Onboarding Call: 30-minute technical overview
  3. Receive Credentials: API key, API secret, and documentation
  4. Choose Integration: GTM or the bearer-token API integration
  5. Go Live: Testing, validation, and production deployment

Next Steps


Questions? Contact our integration team at integration@departcart.com