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

Step 1: Provide Branding Information

Brand Assets Required

Please provide the following branding materials to ensure a seamless customer experience:

Logo Requirements

Color Palette

Typography (Optional)

Brand Guidelines

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:

Required GDS Access

For Sabre Integration

REST API Credentials:

For Amadeus Integration

API Credentials:

Required Access:

Security Requirements

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

Merchant of Record Benefits

As the merchant of record, you maintain:

Stripe Configuration

Required Credentials

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

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

⭐ 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

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

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.

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:

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:

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

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:

See Webhook Documentation for complete event schemas.

Step 7: Testing & Go-Live

Sandbox Testing

  1. Receive sandbox credentials from our team
  2. Test encryption implementation with test data
  3. Verify webhook handling with sample events
  4. Complete end-to-end transaction in sandbox
  5. Performance testing under load

User Acceptance Testing

  1. Internal testing with your team
  2. Stakeholder review of user experience
  3. Brand compliance verification
  4. Mobile responsiveness testing
  5. Cross-browser compatibility testing

Production Deployment

  1. Switch to production credentials
  2. Update DNS/CDN configurations
  3. Monitor initial transactions
  4. Verify webhook notifications
  5. Confirm GDS fulfillment

Launch Checklist

Support During Onboarding

Dedicated Support Team

During onboarding, you'll have access to:

Communication Channels

Documentation & Resources

Post-Launch Support

Ongoing Support Includes

SLA Commitments


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:


Welcome to DepartCart! We're excited to help you maximize your ancillary revenue. 🚀