Partner Onboarding Guide
Welcome to DepartCart
This guide will walk you through the complete onboarding process to integrate DepartCart ancillary services into your booking platform.
Overview
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.
Integration Timeline
Typical integration timeline: 2-4 weeks
- Week 1: Onboarding and credential setup
- Week 2: Technical integration and testing
- Week 3: User acceptance testing and refinements
- Week 4: Production deployment and go-live
Step 1: Provide Branding Information
Brand Assets Required
Please provide the following branding materials to ensure a seamless customer experience:
Logo Requirements
- Primary Logo: Vector format (SVG preferred) or high-resolution PNG (minimum 300x100px)
- Secondary/Icon Logo: Square format for use in compact spaces (minimum 100x100px)
- White/Light Versions: For use on dark backgrounds
- File Formats: SVG, PNG, or AI files
Color Palette
- Primary Brand Color: Hex code (e.g., #1E3A8A)
- Secondary Brand Color: Hex code (e.g., #3B82F6)
- Accent Colors: Additional brand colors (if applicable)
- Text Colors: Primary and secondary text colors
- Background Colors: Light and dark background preferences
Typography (Optional)
- Primary Font: Font family name and web font URL (if custom)
- Secondary Font: For body text (if different from primary)
- Font Weights: Specify which weights to use (normal, bold, etc.)
Brand Guidelines
- Style Guide: PDF or link to brand guidelines
- Usage Rules: Any specific requirements for logo usage
- Color Combinations: Preferred color combinations and restrictions
Example Brand Submission
{
"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"
}
Step 2: Provide GDS Credentials
Supported GDS Systems
DepartCart integrates with major Global Distribution Systems:
- Sabre (Red Apps certified)
- Amadeus (API certified)
- Travelport (Universal API)
Required GDS Access
For Sabre Integration
REST API Credentials:
- Client ID: Your Sabre REST API client ID
- Client Secret: Your Sabre REST API client secret
- Username: Your Sabre username
- Password: Your Sabre password
For Amadeus Integration
API Credentials:
- API Key: Your Amadeus API key
- API Secret: Your Amadeus API secret
- Client ID: Your Amadeus client ID
- Environment: Production or Test environment details
Required Access:
- Flight Offers Search API
- Flight Offers Price API
- Flight Create Orders API
- PNR Management API
- Seat Map Display API
Security Requirements
- IP Whitelisting: Provide our server IPs for GDS access
- SSL Certificates: Ensure proper SSL configuration
- Rate Limiting: Understand GDS rate limits and quotas
- Failover: Configure backup credentials if available
GDS Credential Submission Form
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
Step 3: Payment Gateway Configuration
Supported Payment Gateways
- Stripe (Recommended)
- Adyen
- PayPal
- Authorize.Net
Merchant of Record Benefits
As the merchant of record, you maintain:
- Full Revenue Control: All payments go directly to your account
- Complete Transaction Visibility: Access to all payment data
- Fraud Rule Management: Configure your own fraud prevention
- Chargeback Handling: Direct relationship with payment processor
- Compliance: Maintain your existing PCI compliance
Stripe Configuration
Required Credentials
- Publishable Key: For client-side payment forms
- Secret Key: For server-side API calls
- Webhook Endpoint Secret: For payment event notifications
Webhook Events to Enable
payment_intent.succeeded
payment_intent.payment_failed
charge.dispute.created
invoice.payment_succeeded
customer.subscription.updated
Example Stripe Configuration
{
"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"
}
}
}
Adyen Configuration
Required Credentials
- API Key: For API authentication
- Merchant Account: Your Adyen merchant account ID
- Client Key: For client-side integration
- HMAC Key: For webhook verification
Example Adyen Configuration
{
"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
}
}
}
Payment Flow Architecture
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
Step 4: Encryption Implementation (Recommended)
⭐ We strongly recommend implementing client-side encryption for optimal security, performance, and reliability. This approach eliminates API dependencies and provides faster URL generation.
Benefits of Client-Side Encryption
- No API Dependencies: Generate encrypted URLs without calling our servers
- Better Performance: No network latency or API rate limits
- Higher Reliability: Works even during temporary service outages
- Enhanced Security: PNR data is encrypted before leaving your application
- Batch Processing: Handle thousands of URLs efficiently
Quick Reference
For complete implementation details, see our Partner Encryption Guide which includes production-ready code for Python and C#.
Alternative: API Integration
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"
Testing Your Encryption
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"}'
Step 5: Integration Options
Option A: GTM Lightweight Integration ⭐ Recommended
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:
- Quick deployment
- Minimal development resources
- Standard branding requirements
See our GTM Integration Guide for complete implementation details.
Option B: Full API Integration
Timeline: 2-3 weeks
Development Effort: Moderate to High
Customization: Complete control
Direct API integration with your existing systems. Ideal for:
- Complex custom requirements
- Deep system integration
- Advanced analytics and reporting
See our API Documentation for complete technical details.
Option C: Hybrid Approach
Timeline: 1-2 weeks
Development Effort: Low to Moderate
Customization: Balanced
Combine GTM panels with API webhooks for notifications. Best for:
- Quick deployment with backend integration
- Real-time transaction monitoring
- Custom post-purchase workflows
Step 6: Webhook Configuration
Webhook Endpoint Setup
Provide a secure HTTPS endpoint to receive transaction notifications:
https://api.your-domain.com/webhooks/departcart
Security Requirements
- HTTPS Only: Must use SSL/TLS encryption
- Signature Verification: Implement webhook signature validation
- Idempotency: Handle duplicate events gracefully
- Response Time: Respond within 10 seconds
Example Webhook Handler
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
Webhook Events
You'll receive notifications for:
- Transaction Created: New ancillary purchase initiated
- Payment Completed: Payment successfully processed
- Fulfillment Status: Product fulfillment with airline
- Refund Processed: Refund issued to customer
See Webhook Documentation for complete event schemas.
Step 7: Testing & Go-Live
Sandbox Testing
- Receive sandbox credentials from our team
- Test encryption implementation with test data
- Verify webhook handling with sample events
- Complete end-to-end transaction in sandbox
- Performance testing under load
User Acceptance Testing
- Internal testing with your team
- Stakeholder review of user experience
- Brand compliance verification
- Mobile responsiveness testing
- Cross-browser compatibility testing
Production Deployment
- Switch to production credentials
- Update DNS/CDN configurations
- Monitor initial transactions
- Verify webhook notifications
- Confirm GDS fulfillment
Launch Checklist
- [ ] Branding assets implemented correctly
- [ ] GDS integration tested and working
- [ ] Payment gateway processing successfully
- [ ] Encryption implementation verified
- [ ] GTM/API integration deployed
- [ ] Webhooks receiving and processing events
- [ ] Customer support team trained
- [ ] Monitoring and alerting configured
- [ ] Rollback plan prepared
Support During Onboarding
Dedicated Support Team
During onboarding, you'll have access to:
- Integration Engineer: Technical implementation support
- Account Manager: Overall project coordination
- QA Specialist: Testing and validation assistance
Communication Channels
- Slack Channel: Real-time chat support (invitation provided)
- Email: integration@departcart.com
- Video Calls: Weekly check-ins scheduled
Documentation & Resources
- Technical Documentation: Complete API and integration guides
- Code Examples: Sample implementations in multiple languages
- Best Practices: Proven patterns from successful integrations
- Troubleshooting: Common issues and solutions
Post-Launch Support
Ongoing Support Includes
- Technical Support: Bug fixes and integration issues
- Feature Updates: New products and capabilities
- Performance Monitoring: API performance and uptime tracking
- Business Reviews: Regular performance and optimization meetings
SLA Commitments
- API Uptime: 99.9%
- Support Response: < 4 hours (business days)
- Critical Issues: < 1 hour (24/7)
- Feature Requests: Quarterly roadmap reviews
Next Steps
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:
- Dedicated Slack channel
- Sandbox credentials
- Project timeline
- Technical documentation access
Welcome to DepartCart! We're excited to help you maximize your ancillary revenue. 🚀