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>
- Configure trigger for confirmation/account pages
- Test with sample bookings
Result: Automatic seat selection panels appear on confirmation pages, securely encrypted URLs, fully responsive design.
Option 2: API Integration (Recommended)
⭐ 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:
- Get a bearer token from
POST /generate-bearer-tokenusing yourapi_keyandapi_secret. - 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_secretserver-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.
2. Generate the Seatmap Deeplink
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
Optional: Generate cart & checkout deeplinks
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
- Contact Integration Team: integration@departcart.com
- Schedule Onboarding Call: 30-minute technical overview
- Receive Credentials: API key, API secret, and documentation
- Choose Integration: GTM or the bearer-token API integration
- Go Live: Testing, validation, and production deployment
Next Steps
Questions? Contact our integration team at integration@departcart.com