This guide will walk you through the complete onboarding process to integrate DepartCart ancillary services into your booking platform.
DepartCart provides a complete ancillary commerce platform that enables airlines and travel partners to offer premium services like seat selection, priority support, travel insurance, and more. Our platform handles the entire transaction lifecycle from presentation to fulfillment.
Typical integration timeline: 2-4 weeks
Please provide the following branding materials to ensure a seamless customer experience:
{
"brand_name": "SkylineAir",
"primary_color": "#1E40AF",
"secondary_color": "#3B82F6",
"accent_color": "#10B981",
"text_primary": "#1F2937",
"text_secondary": "#6B7280",
"background_light": "#F9FAFB",
"background_dark": "#111827",
"primary_font": "Montserrat",
"font_url": "https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700",
"logo_primary": "https://assets.skylineair.com/logo-primary.svg",
"logo_icon": "https://assets.skylineair.com/logo-icon.svg"
}
DepartCart integrates with major Global Distribution Systems:
REST API Credentials:
API Credentials:
Required Access:
gds_provider: "sabre" # or "amadeus", "travelport"
environment: "production" # or "test"
# Sabre REST Credentials
rest_credentials:
client_id: "YOUR_CLIENT_ID"
client_secret: "YOUR_CLIENT_SECRET"
username: "YOUR_REST_USERNAME"
password: "YOUR_REST_PASSWORD"
# Network Configuration
network:
allowed_ips: ["52.1.2.3", "52.4.5.6"]
rate_limits:
requests_per_minute: 1000
burst_limit: 100
As the merchant of record, you maintain:
payment_intent.succeeded
payment_intent.payment_failed
charge.dispute.created
invoice.payment_succeeded
customer.subscription.updated
{
"provider": "stripe",
"environment": "production",
"credentials": {
"publishable_key": "pk_live_YOUR_PUBLISHABLE_KEY",
"secret_key": "sk_live_YOUR_SECRET_KEY",
"webhook_secret": "whsec_YOUR_WEBHOOK_SECRET"
},
"settings": {
"currency": "USD",
"capture_method": "automatic",
"payment_methods": ["card", "apple_pay", "google_pay"],
"fraud_rules": {
"enabled": true,
"risk_threshold": "normal"
}
}
}
{
"provider": "adyen",
"environment": "live",
"credentials": {
"api_key": "YOUR_API_KEY",
"merchant_account": "YOUR_MERCHANT_ACCOUNT",
"client_key": "YOUR_CLIENT_KEY",
"hmac_key": "YOUR_HMAC_KEY"
},
"settings": {
"currency": "USD",
"payment_methods": ["scheme", "applepay", "googlepay"],
"fraud_detection": {
"enabled": true,
"risk_score_threshold": 60
}
}
}
sequenceDiagram
participant Customer
participant YourSite
participant DepartCart
participant PaymentGateway
participant GDS
Customer->>YourSite: Select ancillary products
YourSite->>DepartCart: Generate encrypted seatmap URL
Customer->>DepartCart: Access seatmap with encrypted data
DepartCart->>GDS: Retrieve seat availability
Customer->>DepartCart: Select seats and proceed to payment
DepartCart->>PaymentGateway: Process payment via your gateway
PaymentGateway->>YourSite: Payment webhook notification
DepartCart->>GDS: Fulfill seat assignments
DepartCart->>YourSite: Transaction webhook notification
⭐ We strongly recommend implementing client-side encryption for optimal security, performance, and reliability. This approach eliminates API dependencies and provides faster URL generation.
For complete implementation details, see our Partner Encryption Guide which includes production-ready code for Python and C#.
If you prefer to use our API for encryption (not recommended for production), you can call our /generate-encrypted-url endpoint as a fallback option.
import json
import base64
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
def encrypt_passenger_data(pnr, last_name, brand_name, password, salt):
"""Encrypt passenger data for DepartCart API"""
# Prepare data payload
data = {
"pnr": pnr.strip().upper(),
"last_name": last_name.strip().upper(),
"brand": brand_name
}
# Derive encryption key
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt.encode(),
iterations=100000,
)
key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
# Encrypt data
fernet = Fernet(key)
json_data = json.dumps(data)
encrypted_data = fernet.encrypt(json_data.encode())
# Return URL-safe base64 encoded string
return base64.urlsafe_b64encode(encrypted_data).decode()
# Example usage
encrypted_id = encrypt_passenger_data(
pnr="ABC123",
last_name="SMITH",
brand_name="your_brand",
password="your-brand-secret-key-2025",
salt="your_brand_salt_2025"
)
# Create seatmap URL
seatmap_url = f"https://departcart.com/seatmap?id={encrypted_id}&source=api"
using System;
using System.Text;
using System.Text.Json;
using System.Security.Cryptography;
public class DepartCartEncryption
{
public static string EncryptPassengerData(string pnr, string lastName,
string brandName, string password, string salt)
{
// Prepare data payload
var data = new
{
pnr = pnr.Trim().ToUpper(),
last_name = lastName.Trim().ToUpper(),
brand = brandName
};
// Convert to JSON
string jsonData = JsonSerializer.Serialize(data);
byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonData);
// Derive key using PBKDF2
using (var pbkdf2 = new Rfc2898DeriveBytes(password, Encoding.UTF8.GetBytes(salt), 100000))
{
byte[] key = pbkdf2.GetBytes(32);
// Encrypt using AES
using (var aes = Aes.Create())
{
aes.Key = key;
aes.GenerateIV();
using (var encryptor = aes.CreateEncryptor())
{
byte[] encrypted = encryptor.TransformFinalBlock(jsonBytes, 0, jsonBytes.Length);
// Combine IV + encrypted data
byte[] result = new byte[aes.IV.Length + encrypted.Length];
Array.Copy(aes.IV, 0, result, 0, aes.IV.Length);
Array.Copy(encrypted, 0, result, aes.IV.Length, encrypted.Length);
return Convert.ToBase64String(result);
}
}
}
}
}
// Example usage
string encryptedId = DepartCartEncryption.EncryptPassengerData(
pnr: "ABC123",
lastName: "SMITH",
brandName: "your_brand",
password: "your-brand-secret-key-2025",
salt: "your_brand_salt_2025"
);
string seatmapUrl = $"https://departcart.com/seatmap?id={encryptedId}&source=api";
const crypto = require('crypto');
function encryptPassengerData(pnr, lastName, brandName, password, salt) {
// Prepare data payload
const data = {
pnr: pnr.trim().toUpperCase(),
last_name: lastName.trim().toUpperCase(),
brand: brandName
};
// Convert to JSON
const jsonData = JSON.stringify(data);
// Derive key using PBKDF2
const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256');
// Generate IV and encrypt
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encrypted = cipher.update(jsonData, 'utf8', 'base64');
encrypted += cipher.final('base64');
// Combine IV + encrypted data
const combined = Buffer.concat([iv, Buffer.from(encrypted, 'base64')]);
return combined.toString('base64');
}
// Example usage
const encryptedId = encryptPassengerData(
'ABC123',
'SMITH',
'your_brand',
'your-brand-secret-key-2025',
'your_brand_salt_2025'
);
const seatmapUrl = `https://departcart.com/seatmap?id=${encryptedId}&source=api`;
import java.util.Base64;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
public class DepartCartEncryption {
public static String encryptPassengerData(String pnr, String lastName,
String brandName, String password, String salt) throws Exception {
// Prepare data payload
Map<String, String> data = new HashMap<>();
data.put("pnr", pnr.trim().toUpperCase());
data.put("last_name", lastName.trim().toUpperCase());
data.put("brand", brandName);
// Convert to JSON
ObjectMapper mapper = new ObjectMapper();
String jsonData = mapper.writeValueAsString(data);
// Derive key using PBKDF2
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 100000, 256);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] key = factory.generateSecret(spec).getEncoded();
// Generate IV and encrypt
SecureRandom random = new SecureRandom();
byte[] iv = new byte[16];
random.nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(jsonData.getBytes("UTF-8"));
// Combine IV + encrypted data
byte[] result = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(encrypted, 0, result, iv.length, encrypted.length);
return Base64.getEncoder().encodeToString(result);
}
}
// Example usage
String encryptedId = DepartCartEncryption.encryptPassengerData(
"ABC123", "SMITH", "your_brand",
"your-brand-secret-key-2025", "your_brand_salt_2025"
);
String seatmapUrl = "https://departcart.com/seatmap?id=" + encryptedId + "&source=api";
require 'openssl'
require 'base64'
require 'json'
class DepartCartEncryption
def self.encrypt_passenger_data(pnr, last_name, brand_name, password, salt)
# Prepare data payload
data = {
pnr: pnr.strip.upcase,
last_name: last_name.strip.upcase,
brand: brand_name
}
# Convert to JSON
json_data = data.to_json
# Derive key using PBKDF2
key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, 100000, 32, OpenSSL::Digest::SHA256.new)
# Generate IV and encrypt
cipher = OpenSSL::Cipher.new('AES-256-CBC')
cipher.encrypt
cipher.key = key
iv = cipher.random_iv
encrypted = cipher.update(json_data) + cipher.final
# Combine IV + encrypted data
result = iv + encrypted
Base64.strict_encode64(result)
end
end
# Example usage
encrypted_id = DepartCartEncryption.encrypt_passenger_data(
'ABC123', 'SMITH', 'your_brand',
'your-brand-secret-key-2025', 'your_brand_salt_2025'
)
seatmap_url = "https://departcart.com/seatmap?id=#{encrypted_id}&source=api"
Use our test endpoint to verify your implementation:
curl -X POST https://api.departcart.com/v1/test-encryption \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"encrypted_id": "YOUR_ENCRYPTED_ID"}'
Timeline: 1-2 days
Development Effort: Minimal
Customization: Limited but sufficient for most use cases
Deploy seat selection panels using Google Tag Manager. Perfect for:
See our GTM Integration Guide for complete implementation details.
Timeline: 2-3 weeks
Development Effort: Moderate to High
Customization: Complete control
Direct API integration with your existing systems. Ideal for:
See our API Documentation for complete technical details.
Timeline: 1-2 weeks
Development Effort: Low to Moderate
Customization: Balanced
Combine GTM panels with API webhooks for notifications. Best for:
Provide a secure HTTPS endpoint to receive transaction notifications:
https://api.your-domain.com/webhooks/departcart
import hmac
import hashlib
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhooks/departcart', methods=['POST'])
def handle_departcart_webhook():
# Verify signature
payload = request.get_data(as_text=True)
signature = request.headers.get('X-DepartCart-Signature')
if not verify_signature(payload, signature):
return 'Unauthorized', 401
# Process event
event = request.get_json()
process_webhook_event(event)
return 'OK', 200
def verify_signature(payload, signature):
expected = hmac.new(
WEBHOOK_SECRET.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
def process_webhook_event(event):
event_type = event.get('type')
if event_type == 'transaction.completed':
handle_transaction_completed(event['data'])
elif event_type == 'payment.processed':
handle_payment_processed(event['data'])
elif event_type == 'fulfillment.updated':
handle_fulfillment_updated(event['data'])
else:
print(f"Unknown event type: {event_type}")
def handle_transaction_completed(data):
transaction_id = data['transaction_id']
amount = data['amount']
print(f"Transaction {transaction_id} completed for ${amount}")
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
[ApiController]
[Route("webhooks")]
public class WebhookController : ControllerBase
{
private readonly string _webhookSecret;
public WebhookController(IConfiguration configuration)
{
_webhookSecret = configuration["DepartCart:WebhookSecret"];
}
[HttpPost("departcart")]
public async Task<IActionResult> HandleDepartCartWebhook()
{
// Read payload
string payload;
using (var reader = new StreamReader(Request.Body))
{
payload = await reader.ReadToEndAsync();
}
// Verify signature
var signature = Request.Headers["X-DepartCart-Signature"].FirstOrDefault();
if (!VerifySignature(payload, signature))
{
return Unauthorized("Invalid signature");
}
// Process event
var webhookEvent = JsonConvert.DeserializeObject<WebhookEvent>(payload);
ProcessWebhookEvent(webhookEvent);
return Ok("Success");
}
private bool VerifySignature(string payload, string signature)
{
if (string.IsNullOrEmpty(signature) || !signature.StartsWith("sha256="))
{
return false;
}
var expectedSignature = signature.Substring(7); // Remove "sha256=" prefix
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_webhookSecret)))
{
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
var computedSignature = Convert.ToHexString(hash).ToLower();
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expectedSignature),
Encoding.UTF8.GetBytes(computedSignature)
);
}
}
private void ProcessWebhookEvent(WebhookEvent webhookEvent)
{
switch (webhookEvent.Type)
{
case "transaction.completed":
HandleTransactionCompleted(webhookEvent.Data);
break;
case "payment.processed":
HandlePaymentProcessed(webhookEvent.Data);
break;
case "fulfillment.updated":
HandleFulfillmentUpdated(webhookEvent.Data);
break;
default:
Console.WriteLine($"Unknown event type: {webhookEvent.Type}");
break;
}
}
private void HandleTransactionCompleted(dynamic data)
{
var transactionId = data.transaction_id;
var amount = data.amount;
Console.WriteLine($"Transaction {transactionId} completed for ${amount}");
}
private void HandlePaymentProcessed(dynamic data)
{
var paymentId = data.payment_id;
Console.WriteLine($"Payment {paymentId} processed successfully");
}
private void HandleFulfillmentUpdated(dynamic data)
{
var status = data.status;
Console.WriteLine($"Fulfillment status updated to: {status}");
}
}
public class WebhookEvent
{
public string Type { get; set; }
public dynamic Data { get; set; }
}
const express = require('express');
const crypto = require('crypto');
const app = express();
// Middleware to capture raw body for signature verification
app.use('/webhooks/departcart', express.raw({ type: 'application/json' }));
app.post('/webhooks/departcart', (req, res) => {
try {
// Get payload and signature
const payload = req.body.toString();
const signature = req.headers['x-departcart-signature'];
// Verify signature
if (!verifySignature(payload, signature)) {
return res.status(401).send('Unauthorized');
}
// Process event
const event = JSON.parse(payload);
processWebhookEvent(event);
res.status(200).send('OK');
} catch (error) {
console.error('Webhook error:', error);
res.status(500).send('Internal Server Error');
}
});
function verifySignature(payload, signature) {
if (!signature || !signature.startsWith('sha256=')) {
return false;
}
const expectedSignature = signature.slice(7); // Remove 'sha256=' prefix
const computedSignature = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(payload, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(computedSignature, 'hex')
);
}
function processWebhookEvent(event) {
switch (event.type) {
case 'transaction.completed':
handleTransactionCompleted(event.data);
break;
case 'payment.processed':
handlePaymentProcessed(event.data);
break;
case 'fulfillment.updated':
handleFulfillmentUpdated(event.data);
break;
default:
console.log(`Unknown event type: ${event.type}`);
}
}
function handleTransactionCompleted(data) {
const transactionId = data.transaction_id;
const amount = data.amount;
console.log(`Transaction ${transactionId} completed for $${amount}`);
}
function handlePaymentProcessed(data) {
const paymentId = data.payment_id;
console.log(`Payment ${paymentId} processed successfully`);
}
function handleFulfillmentUpdated(data) {
const status = data.status;
console.log(`Fulfillment status updated to: ${status}`);
}
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import org.springframework.beans.factory.annotation.Value;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
@RestController
@RequestMapping("/webhooks")
public class WebhookController {
@Value("${departcart.webhook.secret}")
private String webhookSecret;
private final ObjectMapper objectMapper = new ObjectMapper();
@PostMapping("/departcart")
public ResponseEntity<String> handleDepartCartWebhook(
@RequestBody String payload,
@RequestHeader("X-DepartCart-Signature") String signature) {
try {
// Verify signature
if (!verifySignature(payload, signature)) {
return ResponseEntity.status(401).body("Unauthorized");
}
// Process event
JsonNode event = objectMapper.readTree(payload);
processWebhookEvent(event);
return ResponseEntity.ok("Success");
} catch (Exception e) {
System.err.println("Webhook processing error: " + e.getMessage());
return ResponseEntity.status(500).body("Internal Server Error");
}
}
private boolean verifySignature(String payload, String signature) {
if (signature == null || !signature.startsWith("sha256=")) {
return false;
}
try {
String expectedSignature = signature.substring(7); // Remove "sha256=" prefix
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(
webhookSecret.getBytes(), "HmacSHA256");
mac.init(secretKeySpec);
byte[] hash = mac.doFinal(payload.getBytes());
String computedSignature = bytesToHex(hash);
return MessageDigest.isEqual(
expectedSignature.getBytes(),
computedSignature.getBytes()
);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
System.err.println("Signature verification error: " + e.getMessage());
return false;
}
}
private void processWebhookEvent(JsonNode event) {
String eventType = event.get("type").asText();
JsonNode data = event.get("data");
switch (eventType) {
case "transaction.completed":
handleTransactionCompleted(data);
break;
case "payment.processed":
handlePaymentProcessed(data);
break;
case "fulfillment.updated":
handleFulfillmentUpdated(data);
break;
default:
System.out.println("Unknown event type: " + eventType);
}
}
private void handleTransactionCompleted(JsonNode data) {
String transactionId = data.get("transaction_id").asText();
double amount = data.get("amount").asDouble();
System.out.println("Transaction " + transactionId +
" completed for $" + amount);
}
private void handlePaymentProcessed(JsonNode data) {
String paymentId = data.get("payment_id").asText();
System.out.println("Payment " + paymentId + " processed successfully");
}
private void handleFulfillmentUpdated(JsonNode data) {
String status = data.get("status").asText();
System.out.println("Fulfillment status updated to: " + status);
}
private String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
}
require 'sinatra'
require 'json'
require 'openssl'
# Configure webhook secret
WEBHOOK_SECRET = ENV['WEBHOOK_SECRET']
post '/webhooks/departcart' do
# Get payload and signature
payload = request.body.read
signature = request.env['HTTP_X_DEPARTCART_SIGNATURE']
# Verify signature
unless verify_signature(payload, signature)
halt 401, 'Unauthorized'
end
# Process event
begin
event = JSON.parse(payload)
process_webhook_event(event)
rescue JSON::ParserError => e
puts "JSON parsing error: #{e.message}"
halt 400, 'Bad Request'
rescue StandardError => e
puts "Processing error: #{e.message}"
halt 500, 'Internal Server Error'
end
status 200
body 'OK'
end
def verify_signature(payload, signature)
return false if signature.nil? || !signature.start_with?('sha256=')
expected_signature = signature[7..-1] # Remove 'sha256=' prefix
computed_signature = OpenSSL::HMAC.hexdigest(
OpenSSL::Digest.new('sha256'),
WEBHOOK_SECRET,
payload
)
# Use secure comparison to prevent timing attacks
Rack::Utils.secure_compare(expected_signature, computed_signature)
end
def process_webhook_event(event)
event_type = event['type']
data = event['data']
case event_type
when 'transaction.completed'
handle_transaction_completed(data)
when 'payment.processed'
handle_payment_processed(data)
when 'fulfillment.updated'
handle_fulfillment_updated(data)
else
puts "Unknown event type: #{event_type}"
end
end
def handle_transaction_completed(data)
transaction_id = data['transaction_id']
amount = data['amount']
puts "Transaction #{transaction_id} completed for $#{amount}"
end
def handle_payment_processed(data)
payment_id = data['payment_id']
puts "Payment #{payment_id} processed successfully"
end
def handle_fulfillment_updated(data)
status = data['status']
puts "Fulfillment status updated to: #{status}"
end
# Start the server
if __FILE__ == $0
set :port, 4567
set :bind, '0.0.0.0'
puts "Webhook server starting on port 4567"
end
You'll receive notifications for:
See Webhook Documentation for complete event schemas.
During onboarding, you'll have access to:
Ready to get started? Contact our integration team:
📧 Email: hello@departcart.com
We'll set up your initial onboarding call within 24 hours and provide you with:
Welcome to DepartCart! We're excited to help you maximize your ancillary revenue. 🚀