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
- Authentication & Bearer Tokens
- Generate Seatmap Deeplink
- Generate All Deeplinks
- Seatmap Display
- GTM Integration
- Webhook Notifications
- Error Handling
- 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.
Generate Seatmap Deeplink
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 All Deeplinks
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:
id(string): Encrypted passenger data IDsource(string, optional): Integration source identifier (api,gtm,cart, etc.)
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:
- Minimal development: Just add GTM code
- Auto-detection: Finds PNR/passenger data from page
- Modal interface: No page navigation required
- Responsive design: Works on all devices
Setup Steps:
- Add the GTM code above to your tag manager
- Configure trigger for confirmation/account pages
- Ensure your pages have identifiable booking elements
- 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
transaction.created- New ancillary purchase initiatedtransaction.completed- Payment successful and services confirmedtransaction.failed- Payment or service confirmation failedtransaction.refunded- Full or partial refund processed
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
200- Success400- Bad Request (invalid parameters)401- Unauthorized (invalid or missing credentials)403- Forbidden (insufficient permissions)404- Not Found (resource doesn't exist)422- Unprocessable Entity (validation errors)500- Internal Server Error
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
INVALID_PNR- PNR not found or invalid formatENCRYPTION_ERROR- Failed to decrypt passenger dataBRAND_NOT_CONFIGURED- Brand not set up for ancillary servicesPAYMENT_FAILED- Payment processing errorGDS_ERROR- Unable to communicate with airline reservation system
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
- [ ] π¨ Provide branding assets (logos, color palette)
- [ ] π Share GDS credentials (Sabre/Amadeus access)
- [ ] π³ Configure payment gateway (Stripe/Adyen)
- [ ] π Receive API key, secret, and webhook secrets
- [ ] π Set up webhook endpoint
βοΈ Phase 2: Integration Options
Choose your preferred integration approach
- [ ] πββοΈ GTM Integration (fastest): Add GTM code to confirmation pages
- [ ] π οΈ API Integration (custom): Implement bearer-token auth and direct API calls
- [ ] π Hybrid Approach: GTM for UI, API for custom workflows
π§ͺ Phase 3: Testing
Validate your integration before going live
- [ ] π Test bearer token retrieval with sample data
- [ ] πΊοΈ Verify seatmap loads correctly
- [ ] π’ Test webhook notifications
- [ ] π° Validate payment processing
π― Phase 4: Go Live
Final steps for production deployment
- [ ] π Production credentials configured
- [ ] π Webhook monitoring set up
- [ ] π¨ Error handling implemented
- [ ] π Support contact established
β±οΈ 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
Documentation Links
Support Contacts
- Technical Support: support@departcart.com
- Integration Team: integration@departcart.com
- Account Management: accounts@departcart.com
Testing Tools
- URL Generator: https://departcart.com/generate-encrypted-url
- Encryption Test: https://departcart.com/test-encryption
- API Documentation: https://departcart.com/api/docs
This documentation is updated regularly. For the latest version, visit: https://departcart.com/docs