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
Success Metrics & ROI
Typical Performance
- Conversion Rate: 15-25% of passengers purchase ancillaries
- Average Order Value: $45-85 per transaction
- Implementation Time: 1-14 days depending on integration method
- Revenue Share: Competitive commission structure
Revenue Examples
For an airline with 10,000 monthly passengers:
- Participants: 2,000 passengers (20% conversion)
- Average Purchase: $65 per transaction
- Monthly Revenue: $130,000
- Annual Revenue: $1.56M
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