Guide d'integration complet

Ce guide couvre tout ce dont vous avez besoin pour integrer les services de livraison Dropcolis dans votre application. De l'authentification aux webhooks, vous trouverez des exemples detailles dans plusieurs langages de programmation.

Demarrage rapide

Commencez en 5 minutes

  1. Obtenir les cles API: Connectez-vous a votre Tableau de bord partenaire et creez des cles API
  2. Installer les dependances: Ajouter la bibliotheque HMAC pour la generation de signature
  3. Effectuez votre premier appel: Testez avec une demande de devis
  4. Creer des commandes: Commencez a creer des commandes de livraison

Identifiants de test

Utilisez ces identifiants sandbox pour le developpement:

Cles API Sandbox:
  • Cle publique: pk_test_dropcolis_partner
  • Cle secrete: sk_test_secret_dropcolis_partner
  • URL de base: http://localhost:5001/v1

Authentication

Toutes les requetes API necessitent une authentification HMAC-SHA256. Vous devez inclure trois en-tetes avec chaque requete:

En-tete Description Exemple
X-Api-Key Votre cle API publique pk_test_dropcolis_partner
X-Timestamp Horodatage Unix (secondes depuis l'epoque) 1702051200
X-Signature Signature HMAC-SHA256 avec prefixe v1= v1=abc123...

Generation de signature

La signature protege vos requetes contre la falsification. Voici comment la generer:

Signature Algorithm
// Step 1: Build the string to sign
string_to_sign = timestamp + "\n" + http_method + "\n" + path + "\n" + body

// Step 2: Create HMAC-SHA256 signature
signature = HMAC_SHA256(secret_key, string_to_sign)

// Step 3: Format the header
X-Signature = "v1=" + hex_encode(signature)
Important: Le corps de la requete doit etre exactement la chaine JSON utilisee dans le calcul de la signature. Utilisez une serialisation JSON coherente (pas d'espaces supplementaires, meme ordre des cles).

Classes d'aide a l'authentification

dropcolis_client.py
import hmac
import hashlib
import time
import json
import uuid
import requests

class DropcolisClient:
    """Dropcolis API Client with HMAC authentication"""

    def __init__(self, public_key, secret_key, base_url="http://localhost:5001"):
        self.public_key = public_key
        self.secret_key = secret_key
        self.base_url = base_url

    def _generate_signature(self, method, path, body, timestamp):
        """Generate HMAC-SHA256 signature"""
        string_to_sign = f"{timestamp}\n{method}\n{path}\n{body}"
        signature = hmac.new(
            self.secret_key.encode('utf-8'),
            string_to_sign.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
        return f"v1={signature}"

    def _make_request(self, method, path, data=None, idempotency_key=None):
        """Make authenticated API request"""
        timestamp = str(int(time.time()))
        body = json.dumps(data, separators=(',', ':')) if data else ""

        headers = {
            'Content-Type': 'application/json',
            'X-Api-Key': self.public_key,
            'X-Timestamp': timestamp,
            'X-Signature': self._generate_signature(method, path, body, timestamp)
        }

        if idempotency_key:
            headers['Idempotency-Key'] = idempotency_key

        url = f"{self.base_url}{path}"

        if method == 'GET':
            response = requests.get(url, headers=headers)
        elif method == 'POST':
            response = requests.post(url, headers=headers, data=body)
        elif method == 'DELETE':
            response = requests.delete(url, headers=headers)

        return response.json(), response.status_code

    def get_rate_quote(self, quote_data):
        """Get delivery rate quote"""
        return self._make_request('POST', '/v1/rates/quote', quote_data)

    def create_order(self, order_data):
        """Create a new delivery order"""
        idempotency_key = str(uuid.uuid4())
        return self._make_request('POST', '/v1/orders', order_data, idempotency_key)

    def get_order(self, order_id):
        """Get order details"""
        return self._make_request('GET', f'/v1/orders/{order_id}')

    def cancel_order(self, order_id, reason=None):
        """Cancel an order"""
        data = {'reason': reason} if reason else {}
        return self._make_request('POST', f'/v1/orders/{order_id}/cancel', data)


# Usage Example
if __name__ == "__main__":
    client = DropcolisClient(
        public_key="pk_test_dropcolis_partner",
        secret_key="sk_test_secret_dropcolis_partner"
    )

    # Get a rate quote
    quote, status = client.get_rate_quote({
        "service_level": "standard",
        "pickup": {
            "address": {
                "postal_code": "H1A1A1",
                "city": "Montreal",
                "province": "QC",
                "country": "CA"
            }
        },
        "dropoff": {
            "address": {
                "postal_code": "H2L1P1",
                "city": "Montreal",
                "province": "QC",
                "country": "CA"
            }
        },
        "parcels": [{"weight_kg": 2.5}]
    })

    print(f"Rate: ${quote.get('rate_cents', 0) / 100:.2f} CAD")
dropcolisClient.js
const crypto = require('crypto');
const axios = require('axios');

class DropcolisClient {
    constructor(publicKey, secretKey, baseUrl = 'http://localhost:5001') {
        this.publicKey = publicKey;
        this.secretKey = secretKey;
        this.baseUrl = baseUrl;
    }

    _generateSignature(method, path, body, timestamp) {
        const stringToSign = `${timestamp}\n${method}\n${path}\n${body}`;
        const signature = crypto
            .createHmac('sha256', this.secretKey)
            .update(stringToSign)
            .digest('hex');
        return `v1=${signature}`;
    }

    async _makeRequest(method, path, data = null, idempotencyKey = null) {
        const timestamp = Math.floor(Date.now() / 1000).toString();
        const body = data ? JSON.stringify(data) : '';

        const headers = {
            'Content-Type': 'application/json',
            'X-Api-Key': this.publicKey,
            'X-Timestamp': timestamp,
            'X-Signature': this._generateSignature(method, path, body, timestamp)
        };

        if (idempotencyKey) {
            headers['Idempotency-Key'] = idempotencyKey;
        }

        const config = { headers };
        const url = `${this.baseUrl}${path}`;

        try {
            let response;
            if (method === 'GET') {
                response = await axios.get(url, config);
            } else if (method === 'POST') {
                response = await axios.post(url, data, config);
            } else if (method === 'DELETE') {
                response = await axios.delete(url, config);
            }
            return { data: response.data, status: response.status };
        } catch (error) {
            return {
                data: error.response?.data || { error: error.message },
                status: error.response?.status || 500
            };
        }
    }

    async getRateQuote(quoteData) {
        return this._makeRequest('POST', '/v1/rates/quote', quoteData);
    }

    async createOrder(orderData) {
        const idempotencyKey = crypto.randomUUID();
        return this._makeRequest('POST', '/v1/orders', orderData, idempotencyKey);
    }

    async getOrder(orderId) {
        return this._makeRequest('GET', `/v1/orders/${orderId}`);
    }

    async cancelOrder(orderId, reason = null) {
        const data = reason ? { reason } : {};
        return this._makeRequest('POST', `/v1/orders/${orderId}/cancel`, data);
    }
}

// Usage Example
async function main() {
    const client = new DropcolisClient(
        'pk_test_dropcolis_partner',
        'sk_test_secret_dropcolis_partner'
    );

    const { data, status } = await client.getRateQuote({
        service_level: 'standard',
        pickup: {
            address: { postal_code: 'H1A1A1', city: 'Montreal', province: 'QC', country: 'CA' }
        },
        dropoff: {
            address: { postal_code: 'H2L1P1', city: 'Montreal', province: 'QC', country: 'CA' }
        },
        parcels: [{ weight_kg: 2.5 }]
    });

    console.log(`Rate: $${(data.rate_cents / 100).toFixed(2)} CAD`);
}

module.exports = DropcolisClient;
DropcolisClient.php
<?php

class DropcolisClient {
    private $publicKey;
    private $secretKey;
    private $baseUrl;

    public function __construct($publicKey, $secretKey, $baseUrl = 'http://localhost:5001') {
        $this->publicKey = $publicKey;
        $this->secretKey = $secretKey;
        $this->baseUrl = $baseUrl;
    }

    private function generateSignature($method, $path, $body, $timestamp) {
        $stringToSign = "{$timestamp}\n{$method}\n{$path}\n{$body}";
        $signature = hash_hmac('sha256', $stringToSign, $this->secretKey);
        return "v1={$signature}";
    }

    private function makeRequest($method, $path, $data = null, $idempotencyKey = null) {
        $timestamp = (string) time();
        $body = $data ? json_encode($data, JSON_UNESCAPED_SLASHES) : '';

        $headers = [
            'Content-Type: application/json',
            'X-Api-Key: ' . $this->publicKey,
            'X-Timestamp: ' . $timestamp,
            'X-Signature: ' . $this->generateSignature($method, $path, $body, $timestamp)
        ];

        if ($idempotencyKey) {
            $headers[] = 'Idempotency-Key: ' . $idempotencyKey;
        }

        $ch = curl_init($this->baseUrl . $path);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        } elseif ($method === 'DELETE') {
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
        }

        $response = curl_exec($ch);
        $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return [
            'data' => json_decode($response, true),
            'status' => $statusCode
        ];
    }

    public function getRateQuote($quoteData) {
        return $this->makeRequest('POST', '/v1/rates/quote', $quoteData);
    }

    public function createOrder($orderData) {
        $idempotencyKey = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
            mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000,
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
        );
        return $this->makeRequest('POST', '/v1/orders', $orderData, $idempotencyKey);
    }

    public function getOrder($orderId) {
        return $this->makeRequest('GET', "/v1/orders/{$orderId}");
    }

    public function cancelOrder($orderId, $reason = null) {
        $data = $reason ? ['reason' => $reason] : [];
        return $this->makeRequest('POST', "/v1/orders/{$orderId}/cancel", $data);
    }
}

// Usage Example
$client = new DropcolisClient('pk_test_dropcolis_partner', 'sk_test_secret_dropcolis_partner');

$result = $client->getRateQuote([
    'service_level' => 'standard',
    'pickup' => ['address' => ['postal_code' => 'H1A1A1', 'city' => 'Montreal', 'province' => 'QC', 'country' => 'CA']],
    'dropoff' => ['address' => ['postal_code' => 'H2L1P1', 'city' => 'Montreal', 'province' => 'QC', 'country' => 'CA']],
    'parcels' => [['weight_kg' => 2.5]]
]);

echo "Rate: $" . number_format($result['data']['rate_cents'] / 100, 2) . " CAD\n";
?>
DropcolisClient.java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.http.*;
import java.net.URI;
import java.time.Instant;
import java.util.UUID;

public class DropcolisClient {
    private final String publicKey;
    private final String secretKey;
    private final String baseUrl;
    private final HttpClient httpClient;

    public DropcolisClient(String publicKey, String secretKey) {
        this(publicKey, secretKey, "http://localhost:5001");
    }

    public DropcolisClient(String publicKey, String secretKey, String baseUrl) {
        this.publicKey = publicKey;
        this.secretKey = secretKey;
        this.baseUrl = baseUrl;
        this.httpClient = HttpClient.newHttpClient();
    }

    private String generateSignature(String method, String path, String body, String timestamp)
            throws Exception {
        String stringToSign = timestamp + "\n" + method + "\n" + path + "\n" + body;

        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
        mac.init(secretKeySpec);

        byte[] hmacBytes = mac.doFinal(stringToSign.getBytes());
        StringBuilder hexString = new StringBuilder();
        for (byte b : hmacBytes) {
            hexString.append(String.format("%02x", b));
        }

        return "v1=" + hexString.toString();
    }

    public HttpResponse<String> makeRequest(String method, String path, String body,
            String idempotencyKey) throws Exception {
        String timestamp = String.valueOf(Instant.now().getEpochSecond());
        String signature = generateSignature(method, path, body != null ? body : "", timestamp);

        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + path))
            .header("Content-Type", "application/json")
            .header("X-Api-Key", publicKey)
            .header("X-Timestamp", timestamp)
            .header("X-Signature", signature);

        if (idempotencyKey != null) {
            requestBuilder.header("Idempotency-Key", idempotencyKey);
        }

        if ("POST".equals(method)) {
            requestBuilder.POST(HttpRequest.BodyPublishers.ofString(body != null ? body : ""));
        } else if ("GET".equals(method)) {
            requestBuilder.GET();
        } else if ("DELETE".equals(method)) {
            requestBuilder.DELETE();
        }

        return httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
    }

    public HttpResponse<String> createOrder(String orderJson) throws Exception {
        String idempotencyKey = UUID.randomUUID().toString();
        return makeRequest("POST", "/v1/orders", orderJson, idempotencyKey);
    }

    public HttpResponse<String> getOrder(String orderId) throws Exception {
        return makeRequest("GET", "/v1/orders/" + orderId, null, null);
    }

    // Usage
    public static void main(String[] args) throws Exception {
        DropcolisClient client = new DropcolisClient(
            "pk_test_dropcolis_partner",
            "sk_test_secret_dropcolis_partner"
        );

        String orderJson = """
            {
                "partner_order_id": "JAVA-001",
                "service_level": "standard",
                "pickup": { ... },
                "dropoff": { ... },
                "parcels": [{ "weight_kg": 2.5 }]
            }
            """;

        HttpResponse<String> response = client.createOrder(orderJson);
        System.out.println("Status: " + response.statusCode());
        System.out.println("Body: " + response.body());
    }
}

Apercu de l'API

L'API Dropcolis suit les conventions RESTful. Tous les points de terminaison sont prefixes par /v1.

Points de terminaison disponibles

POST /v1/rates/quote

Obtenir un devis de livraison avant de creer une commande

POST /v1/orders

Creer une nouvelle commande de livraison

GET /v1/orders/{order_id}

Recuperer les details de la commande et le statut actuel

GET /v1/orders

Lister toutes les commandes avec filtres optionnels

POST /v1/orders/{order_id}/cancel

Annuler une commande en attente

POST /v1/webhooks/test

Envoyer un webhook de test a votre point de terminaison

Obtenir des devis

Avant de creer une commande, vous pouvez obtenir un devis pour afficher les tarifs a vos clients.

Get Rate Quote
quote_data = {
    "service_level": "standard",  # standard, express, or same_day
    "pickup": {
        "address": {
            "line1": "123 Main Street",
            "city": "Montreal",
            "province": "QC",
            "postal_code": "H1A1A1",
            "country": "CA"
        }
    },
    "dropoff": {
        "address": {
            "line1": "456 Oak Avenue",
            "city": "Montreal",
            "province": "QC",
            "postal_code": "H2L1P1",
            "country": "CA"
        }
    },
    "parcels": [
        {
            "weight_kg": 2.5,
            "length_cm": 30,
            "width_cm": 20,
            "height_cm": 15
        }
    ]
}

result, status = client.get_rate_quote(quote_data)

if status == 200:
    print(f"Service: {result['service_level']}")
    print(f"Rate: ${result['rate_cents'] / 100:.2f} {result['currency']}")
    print(f"Estimated Delivery: {result['estimated_delivery']}")
    print(f"Transit Days: {result['transit_days']}")
Get Rate Quote
const quoteData = {
    service_level: 'express',
    pickup: {
        address: {
            line1: '123 Main Street',
            city: 'Montreal',
            province: 'QC',
            postal_code: 'H1A1A1',
            country: 'CA'
        }
    },
    dropoff: {
        address: {
            line1: '456 Oak Avenue',
            city: 'Montreal',
            province: 'QC',
            postal_code: 'H2L1P1',
            country: 'CA'
        }
    },
    parcels: [{ weight_kg: 2.5, length_cm: 30, width_cm: 20, height_cm: 15 }]
};

const { data, status } = await client.getRateQuote(quoteData);

if (status === 200) {
    console.log(`Service: ${data.service_level}`);
    console.log(`Rate: $${(data.rate_cents / 100).toFixed(2)} ${data.currency}`);
    console.log(`Estimated Delivery: ${data.estimated_delivery}`);
}
Get Rate Quote
// Java
Map<String, Object> pickup = Map.of(
    "address", Map.of(
        "line1", "123 Main Street",
        "city", "Montreal",
        "province", "QC",
        "postal_code", "H1A1A1",
        "country", "CA"
    )
);

Map<String, Object> dropoff = Map.of(
    "address", Map.of(
        "line1", "456 Oak Avenue",
        "city", "Montreal",
        "province", "QC",
        "postal_code", "H2L1P1",
        "country", "CA"
    )
);

List<Map<String, Object>> parcels = List.of(
    Map.of("weight_kg", 2.5, "length_cm", 30, "width_cm", 20, "height_cm", 15)
);

Map<String, Object> quoteData = Map.of(
    "service_level", "standard",
    "pickup", pickup,
    "dropoff", dropoff,
    "parcels", parcels
);

Map<String, Object> result = client.getRateQuote(quoteData);

System.out.println("Service: " + result.get("service_level"));
System.out.println("Rate: $" + ((Integer) result.get("rate_cents") / 100.0) + " " + result.get("currency"));
System.out.println("Estimated Delivery: " + result.get("estimated_delivery"));
System.out.println("Transit Days: " + result.get("transit_days"));
Get Rate Quote
// PHP
$quoteData = [
    'service_level' => 'standard',
    'pickup' => [
        'address' => [
            'line1' => '123 Main Street',
            'city' => 'Montreal',
            'province' => 'QC',
            'postal_code' => 'H1A1A1',
            'country' => 'CA'
        ]
    ],
    'dropoff' => [
        'address' => [
            'line1' => '456 Oak Avenue',
            'city' => 'Montreal',
            'province' => 'QC',
            'postal_code' => 'H2L1P1',
            'country' => 'CA'
        ]
    ],
    'parcels' => [
        ['weight_kg' => 2.5, 'length_cm' => 30, 'width_cm' => 20, 'height_cm' => 15]
    ]
];

$result = $client->getRateQuote($quoteData);

echo "Service: " . $result['service_level'] . "\n";
echo "Rate: $" . number_format($result['rate_cents'] / 100, 2) . " " . $result['currency'] . "\n";
echo "Estimated Delivery: " . $result['estimated_delivery'] . "\n";
echo "Transit Days: " . $result['transit_days'] . "\n";
cURL
curl -X POST "http://localhost:5001/v1/rates/quote" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: pk_test_dropcolis_partner" \
  -H "X-Timestamp: $(date +%s)" \
  -H "X-Signature: v1=YOUR_SIGNATURE" \
  -d '{
    "service_level": "standard",
    "pickup": {"address": {"postal_code": "H1A1A1", "city": "Montreal", "province": "QC", "country": "CA"}},
    "dropoff": {"address": {"postal_code": "H2L1P1", "city": "Montreal", "province": "QC", "country": "CA"}},
    "parcels": [{"weight_kg": 2.5}]
  }'

Response

Rate Quote Response
{
    "quote_id": "qt_abc123def456",
    "service_level": "standard",
    "rate_cents": 1299,
    "currency": "CAD",
    "estimated_delivery": "2025-11-10T17:00:00-05:00",
    "transit_days": 2,
    "valid_until": "2025-11-08T23:59:59Z"
}

Creer des commandes

Creez des commandes de livraison avec les details de ramassage et de livraison, les informations sur les colis et les preferences optionnelles.

Idempotence: Incluez toujours un en-tete Idempotency-Key pour eviter les commandes en double. Si vous reessayez une requete avec la meme cle, vous obtiendrez la reponse originale.

Champs requis

ChampTypeDescription
partner_order_idstringVotre identifiant de commande unique
service_levelstringstandard, express, ou same_day
pickupobjectLieu de ramassage avec nom, telephone, courriel, adresse, fenetre horaire
dropoffobjectLieu de livraison avec la meme structure que le ramassage
parcelsarray1-50 colis avec dimensions, poids et valeur

Exemple de commande complete

Create Order - Python
order_data = {
    "partner_order_id": "ORD-2025-001234",
    "reference": "Invoice #55555",
    "service_level": "standard",

    # Cash on delivery (optional)
    "cod": {
        "amount_cents": 0,  # Set > 0 for COD
        "currency": "CAD"
    },

    # Insurance coverage (optional)
    "insurance": {
        "amount_cents": 15000,  # $150.00 coverage
        "currency": "CAD"
    },

    # Pickup details
    "pickup": {
        "name": "Warehouse Montreal",
        "phone": "+1-514-555-0001",
        "email": "warehouse@yourcompany.com",
        "address": {
            "line1": "123 Industrial Blvd",
            "line2": "Unit 5",
            "city": "Montreal",
            "province": "QC",
            "postal_code": "H1A1A1",
            "country": "CA"
        },
        "time_window": {
            "start": "2025-11-08T09:00:00-05:00",
            "end": "2025-11-08T12:00:00-05:00"
        },
        "instructions": "Loading dock at rear entrance"
    },

    # Delivery details
    "dropoff": {
        "name": "John Smith",
        "phone": "+1-438-555-0002",
        "email": "john.smith@customer.com",
        "address": {
            "line1": "456 Residential Street",
            "line2": "Apt 302",
            "city": "Montreal",
            "province": "QC",
            "postal_code": "H2L1P1",
            "country": "CA"
        },
        "time_window": {
            "start": "2025-11-08T14:00:00-05:00",
            "end": "2025-11-08T18:00:00-05:00"
        },
        "instructions": "Leave with concierge if not home"
    },

    # Parcels
    "parcels": [
        {
            "parcel_id": "PKG-001",
            "description": "Electronics - Laptop",
            "length_cm": 40,
            "width_cm": 30,
            "height_cm": 10,
            "weight_kg": 3.2,
            "value_cents": 150000,  # $1500 declared value
            "fragile": True,
            "barcode": "1234567890"
        },
        {
            "parcel_id": "PKG-002",
            "description": "Accessories",
            "length_cm": 20,
            "width_cm": 15,
            "height_cm": 10,
            "weight_kg": 0.5,
            "value_cents": 5000
        }
    ],

    # General instructions
    "instructions": "Handle with care - fragile electronics",

    # Custom metadata (stored but not processed)
    "metadata": {
        "customer_id": "CUST-12345",
        "order_source": "website",
        "priority": "high"
    }
}

result, status = client.create_order(order_data)

if status == 201:
    print(f"Order created successfully!")
    print(f"Order ID: {result['order_id']}")
    print(f"Tracking URL: {result['tracking_url']}")
    print(f"Status: {result['status']}")
else:
    print(f"Error: {result.get('error', {}).get('message')}")
Create Order - JavaScript
const orderData = {
    partner_order_id: 'ORD-2025-001234',
    reference: 'Invoice #55555',
    service_level: 'standard',

    cod: { amount_cents: 0, currency: 'CAD' },
    insurance: { amount_cents: 15000, currency: 'CAD' },

    pickup: {
        name: 'Warehouse Montreal',
        phone: '+1-514-555-0001',
        email: 'warehouse@yourcompany.com',
        address: {
            line1: '123 Industrial Blvd',
            line2: 'Unit 5',
            city: 'Montreal',
            province: 'QC',
            postal_code: 'H1A1A1',
            country: 'CA'
        },
        time_window: {
            start: '2025-11-08T09:00:00-05:00',
            end: '2025-11-08T12:00:00-05:00'
        },
        instructions: 'Loading dock at rear entrance'
    },

    dropoff: {
        name: 'John Smith',
        phone: '+1-438-555-0002',
        email: 'john.smith@customer.com',
        address: {
            line1: '456 Residential Street',
            line2: 'Apt 302',
            city: 'Montreal',
            province: 'QC',
            postal_code: 'H2L1P1',
            country: 'CA'
        },
        time_window: {
            start: '2025-11-08T14:00:00-05:00',
            end: '2025-11-08T18:00:00-05:00'
        },
        instructions: 'Leave with concierge if not home'
    },

    parcels: [{
        parcel_id: 'PKG-001',
        description: 'Electronics - Laptop',
        length_cm: 40, width_cm: 30, height_cm: 10,
        weight_kg: 3.2,
        value_cents: 150000,
        fragile: true,
        barcode: '1234567890'
    }],

    instructions: 'Handle with care - fragile electronics',
    metadata: { customer_id: 'CUST-12345', order_source: 'website' }
};

const { data, status } = await client.createOrder(orderData);

if (status === 201) {
    console.log('Order created!');
    console.log(`Order ID: ${data.order_id}`);
    console.log(`Tracking: ${data.tracking_url}`);
} else {
    console.error(`Error: ${data.error?.message}`);
}
Create Order - Java
// Java - Create Order
Map<String, Object> pickup = new HashMap<>();
pickup.put("name", "Warehouse Montreal");
pickup.put("phone", "+1-514-555-0001");
pickup.put("email", "warehouse@yourcompany.com");
pickup.put("address", Map.of(
    "line1", "123 Industrial Blvd",
    "line2", "Unit 5",
    "city", "Montreal",
    "province", "QC",
    "postal_code", "H1A1A1",
    "country", "CA"
));
pickup.put("time_window", Map.of(
    "start", "2025-11-08T09:00:00-05:00",
    "end", "2025-11-08T12:00:00-05:00"
));
pickup.put("instructions", "Loading dock at rear entrance");

Map<String, Object> dropoff = new HashMap<>();
dropoff.put("name", "John Smith");
dropoff.put("phone", "+1-438-555-0002");
dropoff.put("email", "john.smith@customer.com");
dropoff.put("address", Map.of(
    "line1", "456 Residential Street",
    "line2", "Apt 302",
    "city", "Montreal",
    "province", "QC",
    "postal_code", "H2L1P1",
    "country", "CA"
));
dropoff.put("time_window", Map.of(
    "start", "2025-11-08T14:00:00-05:00",
    "end", "2025-11-08T18:00:00-05:00"
));
dropoff.put("instructions", "Leave with concierge if not home");

List<Map<String, Object>> parcels = List.of(
    Map.of(
        "parcel_id", "PKG-001",
        "description", "Electronics - Laptop",
        "length_cm", 40, "width_cm", 30, "height_cm", 10,
        "weight_kg", 3.2,
        "value_cents", 150000,
        "fragile", true,
        "barcode", "1234567890"
    )
);

Map<String, Object> orderData = new HashMap<>();
orderData.put("partner_order_id", "ORD-2025-001234");
orderData.put("reference", "Invoice #55555");
orderData.put("service_level", "standard");
orderData.put("cod", Map.of("amount_cents", 0, "currency", "CAD"));
orderData.put("insurance", Map.of("amount_cents", 15000, "currency", "CAD"));
orderData.put("pickup", pickup);
orderData.put("dropoff", dropoff);
orderData.put("parcels", parcels);
orderData.put("instructions", "Handle with care - fragile electronics");

Map<String, Object> result = client.createOrder(orderData);

System.out.println("Order created successfully!");
System.out.println("Order ID: " + result.get("order_id"));
System.out.println("Tracking URL: " + result.get("tracking_url"));
System.out.println("Status: " + result.get("status"));
Create Order - PHP
// PHP - Create Order
$orderData = [
    'partner_order_id' => 'ORD-2025-001234',
    'reference' => 'Invoice #55555',
    'service_level' => 'standard',

    'cod' => ['amount_cents' => 0, 'currency' => 'CAD'],
    'insurance' => ['amount_cents' => 15000, 'currency' => 'CAD'],

    'pickup' => [
        'name' => 'Warehouse Montreal',
        'phone' => '+1-514-555-0001',
        'email' => 'warehouse@yourcompany.com',
        'address' => [
            'line1' => '123 Industrial Blvd',
            'line2' => 'Unit 5',
            'city' => 'Montreal',
            'province' => 'QC',
            'postal_code' => 'H1A1A1',
            'country' => 'CA'
        ],
        'time_window' => [
            'start' => '2025-11-08T09:00:00-05:00',
            'end' => '2025-11-08T12:00:00-05:00'
        ],
        'instructions' => 'Loading dock at rear entrance'
    ],

    'dropoff' => [
        'name' => 'John Smith',
        'phone' => '+1-438-555-0002',
        'email' => 'john.smith@customer.com',
        'address' => [
            'line1' => '456 Residential Street',
            'line2' => 'Apt 302',
            'city' => 'Montreal',
            'province' => 'QC',
            'postal_code' => 'H2L1P1',
            'country' => 'CA'
        ],
        'time_window' => [
            'start' => '2025-11-08T14:00:00-05:00',
            'end' => '2025-11-08T18:00:00-05:00'
        ],
        'instructions' => 'Leave with concierge if not home'
    ],

    'parcels' => [
        [
            'parcel_id' => 'PKG-001',
            'description' => 'Electronics - Laptop',
            'length_cm' => 40,
            'width_cm' => 30,
            'height_cm' => 10,
            'weight_kg' => 3.2,
            'value_cents' => 150000,
            'fragile' => true,
            'barcode' => '1234567890'
        ]
    ],

    'instructions' => 'Handle with care - fragile electronics',
    'metadata' => ['customer_id' => 'CUST-12345', 'order_source' => 'website']
];

$result = $client->createOrder($orderData);

if (isset($result['order_id'])) {
    echo "Order created successfully!\n";
    echo "Order ID: " . $result['order_id'] . "\n";
    echo "Tracking URL: " . $result['tracking_url'] . "\n";
    echo "Status: " . $result['status'] . "\n";
} else {
    echo "Error: " . $result['error']['message'] . "\n";
}

Response

Create Order Response (201 Created)
{
    "order_id": "dc_9f7a1f2e3d4c5b6a",
    "partner_order_id": "ORD-2025-001234",
    "status": "pending",
    "service_level": "standard",
    "tracking_url": "http://localhost:5001/track/dc_9f7a1f2e3d4c5b6a",
    "pickup": {
        "name": "Warehouse Montreal",
        "phone": "+1-514-555-0001",
        "email": "warehouse@yourcompany.com",
        "address": {
            "street": "100 Rue Saint-Paul",
            "city": "Montreal",
            "province": "QC",
            "postal_code": "H2Y1Z3",
            "country": "CA"
        },
        "time_window": {
            "start": "2025-11-08T09:00:00-05:00",
            "end": "2025-11-08T12:00:00-05:00"
        }
    },
    "dropoff": {
        "name": "Jean Tremblay",
        "phone": "+1-514-555-1234",
        "email": "jean.tremblay@email.com",
        "address": {
            "street": "456 Boulevard Rosemont",
            "unit": "Apt 302",
            "city": "Montreal",
            "province": "QC",
            "postal_code": "H2S2K1",
            "country": "CA"
        },
        "time_window": {
            "start": "2025-11-08T14:00:00-05:00",
            "end": "2025-11-08T18:00:00-05:00"
        }
    },
    "parcels": [
        {
            "parcel_id": "prc_abc123",
            "description": "MacBook Pro 16-inch",
            "weight_kg": 2.5,
            "dimensions": {
                "length_cm": 40,
                "width_cm": 30,
                "height_cm": 10
            },
            "value_cents": 299900,
            "currency": "CAD"
        }
    ],
    "rate_cents": 1499,
    "currency": "CAD",
    "estimated_delivery": "2025-11-08T18:00:00-05:00",
    "created_at": "2025-11-08T08:30:00-05:00",
    "updated_at": "2025-11-08T08:30:00-05:00"
}

Suivre les commandes

Recuperez le statut actuel et l'historique complet de toute commande.

Get Order Status
# Python
result, status = client.get_order("dc_9f7a1f2e3d4c5b6a")

print(f"Status: {result['status']}")
print(f"Last Update: {result['updated_at']}")

# Status history
for event in result.get('status_history', []):
    print(f"  {event['timestamp']}: {event['status']}")
Get Order Status
// JavaScript
const { data, status } = await client.getOrder("dc_9f7a1f2e3d4c5b6a");

console.log(`Status: ${data.status}`);
console.log(`Last Update: ${data.updated_at}`);

// Status history
if (data.status_history) {
    data.status_history.forEach(event => {
        console.log(`  ${event.timestamp}: ${event.status}`);
    });
}
Get Order Status
// Java
Map<String, Object> result = client.getOrder("dc_9f7a1f2e3d4c5b6a");

System.out.println("Status: " + result.get("status"));
System.out.println("Last Update: " + result.get("updated_at"));

// Status history
List<Map<String, Object>> history =
    (List<Map<String, Object>>) result.get("status_history");
if (history != null) {
    for (Map<String, Object> event : history) {
        System.out.println("  " + event.get("timestamp") + ": " + event.get("status"));
    }
}
Get Order Status
// PHP
$result = $client->getOrder("dc_9f7a1f2e3d4c5b6a");

echo "Status: " . $result['status'] . "\n";
echo "Last Update: " . $result['updated_at'] . "\n";

// Status history
if (isset($result['status_history'])) {
    foreach ($result['status_history'] as $event) {
        echo "  " . $event['timestamp'] . ": " . $event['status'] . "\n";
    }
}

Statuts des commandes

StatutDescription
pendingCommande recue, en attente de traitement
acceptedCommande acceptee par Dropcolis
courier_assignedCoursier assigne
picked_upColis ramasse a l'origine
in_transitColis en transit
out_for_deliveryEn cours de livraison finale
deliveredLivre avec succes
failedTentative de livraison echouee
cancelledCommande annulee

Page de suivi publique

Partagez le tracking_url avec vos clients. Ils peuvent suivre leur livraison sans authentification:

Tracking URL
http://localhost:5001/track/dc_9f7a1f2e3d4c5b6a

Annuler les commandes

Annulez une commande avant qu'elle ne soit ramassee. Les commandes deja en transit ne peuvent pas etre annulees.

Cancel Order
# Python
result, status = client.cancel_order(
    order_id="dc_9f7a1f2e3d4c5b6a",
    reason="Customer requested cancellation"
)

if status == 200:
    print("Order cancelled successfully")
else:
    print(f"Cannot cancel: {result['error']['message']}")
Cancel Order
// JavaScript
try {
    const { data, status } = await client.cancelOrder(
        "dc_9f7a1f2e3d4c5b6a",
        "Customer requested cancellation"
    );

    if (status === 200) {
        console.log("Order cancelled successfully");
    }
} catch (error) {
    console.log(`Cannot cancel: ${error.response.data.error.message}`);
}
Cancel Order
// Java
try {
    Map<String, Object> result = client.cancelOrder(
        "dc_9f7a1f2e3d4c5b6a",
        "Customer requested cancellation"
    );
    System.out.println("Order cancelled successfully");
} catch (ApiException e) {
    System.out.println("Cannot cancel: " + e.getMessage());
}
Cancel Order
// PHP
$result = $client->cancelOrder(
    "dc_9f7a1f2e3d4c5b6a",
    "Customer requested cancellation"
);

if (isset($result['status']) && $result['status'] === 'cancelled') {
    echo "Order cancelled successfully\n";
} else {
    echo "Cannot cancel: " . $result['error']['message'] . "\n";
}
Cancellation Policy: Orders can only be cancelled while in pending or accepted status. Once a courier is assigned or the package is picked up, cancellation is not possible.

Configuration des webhooks

Les webhooks notifient votre systeme en temps reel lorsque le statut d'une commande change. Au lieu d'interroger l'API, vous recevez des notifications instantanees.

Configuration de votre point de terminaison webhook

  1. Creez un point de terminaison HTTPS sur votre serveur pour recevoir les evenements webhook
  2. Configurez votre URL webhook dans le tableau de bord partenaire
  3. Verifiez les signatures webhook pour garantir l'authenticite
  4. Repondez avec 200 OK pour accuser reception

Exemple de gestionnaire webhook

webhook_handler.py
from flask import Flask, request, jsonify
import hmac
import hashlib

app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret"

def verify_signature(payload, signature, secret):
    """Verify the webhook signature"""
    expected = hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()

    # Extract signature value (format: "v1=signature")
    if signature.startswith('v1='):
        signature = signature[3:]

    return hmac.compare_digest(expected, signature)

@app.route('/webhooks/dropcolis', methods=['POST'])
def handle_webhook():
    # Get signature from header
    signature = request.headers.get('Drop-Signature', '')

    # Verify signature
    if not verify_signature(request.data, signature, WEBHOOK_SECRET):
        return jsonify({'error': 'Invalid signature'}), 401

    # Parse event
    event = request.json
    event_type = event.get('type')
    event_data = event.get('data', {})

    # Handle different event types
    if event_type == 'order.accepted':
        print(f"Order {event_data['order_id']} accepted")
        # Update your order status in database

    elif event_type == 'courier.assigned':
        courier = event_data.get('courier', {})
        print(f"Courier {courier.get('name')} assigned")
        # Notify customer about courier assignment

    elif event_type == 'order.picked_up':
        print(f"Order {event_data['order_id']} picked up")
        # Send SMS/email notification

    elif event_type == 'order.out_for_delivery':
        print(f"Order out for delivery, ETA: {event_data.get('eta')}")
        # Send "out for delivery" notification

    elif event_type == 'order.delivered':
        print(f"Order {event_data['order_id']} delivered!")
        # Mark order as complete, send confirmation

    elif event_type == 'order.failed':
        reason = event_data.get('failure_reason')
        print(f"Delivery failed: {reason}")
        # Handle failed delivery, contact customer

    # Always return 200 to acknowledge receipt
    return jsonify({'received': True}), 200

if __name__ == '__main__':
    app.run(port=8000)
webhookHandler.js
const express = require('express');
const crypto = require('crypto');

const app = express();
const WEBHOOK_SECRET = 'your_webhook_secret';

// Parse raw body for signature verification
app.use('/webhooks/dropcolis', express.raw({ type: 'application/json' }));

function verifySignature(payload, signature, secret) {
    const expected = crypto
        .createHmac('sha256', secret)
        .update(payload)
        .digest('hex');

    // Extract signature value (format: "v1=signature")
    const sig = signature.startsWith('v1=') ? signature.slice(3) : signature;

    return crypto.timingSafeEqual(
        Buffer.from(expected),
        Buffer.from(sig)
    );
}

app.post('/webhooks/dropcolis', (req, res) => {
    const signature = req.headers['drop-signature'] || '';

    // Verify signature
    if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
        return res.status(401).json({ error: 'Invalid signature' });
    }

    const event = JSON.parse(req.body.toString());
    const { type, data } = event;

    switch (type) {
        case 'order.accepted':
            console.log(`Order ${data.order_id} accepted`);
            break;

        case 'courier.assigned':
            console.log(`Courier ${data.courier?.name} assigned`);
            break;

        case 'order.delivered':
            console.log(`Order ${data.order_id} delivered!`);
            // Mark complete, send confirmation
            break;

        case 'order.failed':
            console.log(`Delivery failed: ${data.failure_reason}`);
            // Handle failed delivery
            break;
    }

    res.json({ received: true });
});

app.listen(8000, () => console.log('Webhook server running on port 8000'));
WebhookController.java
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.util.Map;

@RestController
@RequestMapping("/webhooks")
public class WebhookController {

    private static final String WEBHOOK_SECRET = "your_webhook_secret";

    @PostMapping("/dropcolis")
    public ResponseEntity<Map<String, Boolean>> handleWebhook(
            @RequestHeader("Drop-Signature") String signature,
            @RequestBody String payload) {

        // Verify signature
        if (!verifySignature(payload, signature, WEBHOOK_SECRET)) {
            return ResponseEntity.status(401).body(Map.of("received", false));
        }

        // Parse event
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> event = mapper.readValue(payload, Map.class);
        String type = (String) event.get("type");
        Map<String, Object> data = (Map<String, Object>) event.get("data");

        // Handle different event types
        switch (type) {
            case "order.accepted":
                System.out.println("Order " + data.get("order_id") + " accepted");
                break;

            case "courier.assigned":
                Map<String, Object> courier = (Map<String, Object>) data.get("courier");
                System.out.println("Courier " + courier.get("name") + " assigned");
                break;

            case "order.picked_up":
                System.out.println("Order " + data.get("order_id") + " picked up");
                break;

            case "order.out_for_delivery":
                System.out.println("Order out for delivery, ETA: " + data.get("eta"));
                break;

            case "order.delivered":
                System.out.println("Order " + data.get("order_id") + " delivered!");
                // Mark order as complete
                break;

            case "order.failed":
                System.out.println("Delivery failed: " + data.get("failure_reason"));
                // Handle failed delivery
                break;
        }

        return ResponseEntity.ok(Map.of("received", true));
    }

    private boolean verifySignature(String payload, String signature, String secret) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(
                secret.getBytes("UTF-8"), "HmacSHA256"
            );
            mac.init(secretKey);

            byte[] hash = mac.doFinal(payload.getBytes("UTF-8"));
            String expected = bytesToHex(hash);

            // Extract signature value (format: "v1=signature")
            String sig = signature.startsWith("v1=") ? signature.substring(3) : signature;

            return MessageDigest.isEqual(expected.getBytes(), sig.getBytes());
        } catch (Exception e) {
            return false;
        }
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}
webhook.php
<?php
$webhookSecret = 'your_webhook_secret';

// Get raw payload and signature
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_DROP_SIGNATURE'] ?? '';

// Verify signature
function verifySignature($payload, $signature, $secret) {
    $expected = hash_hmac('sha256', $payload, $secret);

    // Extract signature value (format: "v1=signature")
    if (strpos($signature, 'v1=') === 0) {
        $signature = substr($signature, 3);
    }

    return hash_equals($expected, $signature);
}

if (!verifySignature($payload, $signature, $webhookSecret)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

// Parse event
$event = json_decode($payload, true);
$type = $event['type'];
$data = $event['data'];

switch ($type) {
    case 'order.accepted':
        error_log("Order {$data['order_id']} accepted");
        break;

    case 'courier.assigned':
        error_log("Courier {$data['courier']['name']} assigned");
        break;

    case 'order.delivered':
        error_log("Order {$data['order_id']} delivered!");
        // Mark complete in database
        break;

    case 'order.failed':
        error_log("Delivery failed: {$data['failure_reason']}");
        // Handle failed delivery
        break;
}

// Return 200 OK
http_response_code(200);
echo json_encode(['received' => true]);
?>

Types d'evenements webhook

Event TypeDescriptionChamps de donnees
order.accepted Commande acceptee par le systeme order_id, partner_order_id, status
courier.assigned Coursier assigne a la commande order_id, courier (name, phone)
order.picked_up Colis ramasse order_id, picked_up_at
order.in_transit Colis en transit order_id, eta
order.out_for_delivery En cours de livraison finale order_id, eta, courier
order.delivered Livre avec succes order_id, delivered_at, signature_url
order.failed Livraison echouee order_id, failure_reason, next_attempt
order.cancelled Commande annulee order_id, cancelled_by, reason

Signature Verification

Verifiez toujours les signatures webhook pour vous assurer que les evenements proviennent de Dropcolis:

Signature Verification
// Webhook headers sent by Dropcolis:
// - Drop-Event-Id: Unique event UUID
// - Drop-Event-Timestamp: Event timestamp
// - Drop-Signature: HMAC-SHA256 signature (v1=hex)

// Signature is calculated as:
signature = HMAC_SHA256(webhook_secret, raw_request_body)

// Compare with Drop-Signature header (after removing "v1=" prefix)
Avertissement de securite: Ne sautez jamais la verification de signature en production! Des attaquants pourraient envoyer de faux evenements a votre point de terminaison webhook.

Error Handling

Codes de statut HTTP

CodeSignificationAction
200SuccesRequete terminee
201CreeRessource creee (commandes)
400Requete incorrecteCorrigez le format de la requete
401Non autoriseVerifiez les cles API/signature
404Non trouveLa ressource n'existe pas
409ConflitID de commande en double
422Erreur de validationCorrigez les problemes de validation des donnees
429Limite de debit atteinteRalentissez, reessayez plus tard
500Erreur serveurReessayez avec delai

Format de reponse d'erreur

Error Response
{
    "error": {
        "code": "validation_error",
        "message": "Invalid postal code format",
        "details": {
            "field": "dropoff.address.postal_code",
            "value": "INVALID",
            "expected": "Canadian postal code (e.g., H1A1A1)"
        }
    },
    "request_id": "req_abc123def456"
}

Strategie de nouvelle tentative

Exponential Backoff - Python
import time

def make_request_with_retry(func, max_retries=3):
    for attempt in range(max_retries):
        result, status = func()

        if status < 500 and status != 429:
            return result, status

        # Exponential backoff: 1s, 2s, 4s
        wait_time = 2 ** attempt
        print(f"Retrying in {wait_time}s...")
        time.sleep(wait_time)

    return result, status  # Return last response
Exponential Backoff - JavaScript
async function makeRequestWithRetry(requestFunc, maxRetries = 3) {
    let lastResult, lastStatus;

    for (let attempt = 0; attempt < maxRetries; attempt++) {
        const { data, status } = await requestFunc();
        lastResult = data;
        lastStatus = status;

        if (status < 500 && status !== 429) {
            return { data, status };
        }

        // Exponential backoff: 1s, 2s, 4s
        const waitTime = Math.pow(2, attempt) * 1000;
        console.log(`Retrying in ${waitTime / 1000}s...`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
    }

    return { data: lastResult, status: lastStatus };
}
Exponential Backoff - Java
public <T> ApiResponse<T> makeRequestWithRetry(
        Supplier<ApiResponse<T>> requestFunc,
        int maxRetries) {

    ApiResponse<T> lastResponse = null;

    for (int attempt = 0; attempt < maxRetries; attempt++) {
        lastResponse = requestFunc.get();
        int status = lastResponse.getStatus();

        if (status < 500 && status != 429) {
            return lastResponse;
        }

        // Exponential backoff: 1s, 2s, 4s
        long waitTime = (long) Math.pow(2, attempt) * 1000;
        System.out.println("Retrying in " + (waitTime / 1000) + "s...");

        try {
            Thread.sleep(waitTime);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            break;
        }
    }

    return lastResponse;
}
Exponential Backoff - PHP
function makeRequestWithRetry(callable $requestFunc, int $maxRetries = 3): array
{
    $lastResult = null;
    $lastStatus = null;

    for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
        $response = $requestFunc();
        $lastResult = $response['data'];
        $lastStatus = $response['status'];

        if ($lastStatus < 500 && $lastStatus !== 429) {
            return $response;
        }

        // Exponential backoff: 1s, 2s, 4s
        $waitTime = pow(2, $attempt);
        echo "Retrying in {$waitTime}s...\n";
        sleep($waitTime);
    }

    return ['data' => $lastResult, 'status' => $lastStatus];
}

Best Practices

Securite

  • Gardez les secrets en securite: N'exposez jamais les cles API dans le code cote client
  • Utilisez HTTPS: Utilisez toujours HTTPS en production
  • Verifiez les webhooks: Validez toujours les signatures webhook
  • Effectuez une rotation des cles: Effectuez periodiquement une rotation des cles API

Fiabilite

  • Utilisez des cles d'idempotence: Evitez les commandes en double lors des nouvelles tentatives
  • Implementez les nouvelles tentatives: Utilisez un delai exponentiel pour les erreurs transitoires
  • Gerez les delais d'attente: Definissez des delais d'attente de requete appropries (30s recommande)
  • Journalisez les ID de requete: Stockez request_id pour le debogage

Performance

  • Regroupez intelligemment: Evitez de creer trop de commandes simultanement
  • Mettez en cache les devis: Les devis sont valides pendant 24 heures
  • Utilisez les webhooks: Preferez les webhooks a l'interrogation pour les mises a jour de statut

SDK & Libraries

SDK officiels et communautaires pour les langages populaires:

Bientot disponible: Les SDK officiels pour Python, JavaScript, PHP et Ruby sont en developpement. En attendant, utilisez les classes d'aide fournies dans ce guide.

Ressources

Ouvrir Swagger UI Tableau de bord partenaire