Skip to main content

Integration Examples

Real-world code examples and integration patterns for implementing the Referral Partner API in your platform.

Overview

This guide provides practical code examples in multiple programming languages showing how to integrate Debitura's Referral Partner API into your SaaS platform.

Quick Start Examples

Node.js / TypeScript

Complete integration example using Node.js:

import axios from 'axios';

const DEBITURA_API_KEY = process.env.DEBITURA_API_KEY;
const DEBITURA_API_URL = 'https://api.debitura.com/v1';

// Link a customer to Debitura
async function linkCustomer(customer: {
id: string;
companyName: string;
country: string;
email: string;
}) {
try {
const response = await axios.post(
`${DEBITURA_API_URL}/referral-partners/clients`,
{
external_id: customer.id,
company_name: customer.companyName,
country: customer.country,
email: customer.email,
onboarding_type: 'api_only',
},
{
headers: {
Authorization: `Bearer ${DEBITURA_API_KEY}`,
'Content-Type': 'application/json',
},
}
);

const { client_id, bearer_token } = response.data;

// Store bearer token securely
await storeEncrypted(customer.id, {
client_id,
bearer_token,
});

return { client_id, bearer_token };
} catch (error) {
if (error.response?.status === 409) {
// Handle conflict - client already exists
console.log('Client already exists:', error.response.data);
// Attempt to claim if possible
return handleConflict(error.response.data);
}
throw error;
}
}

// Create a debt collection case
async function createCase(customerId: string, caseData: {
debtorName: string;
debtorEmail: string;
debtorCountry: string;
amount: number;
currency: string;
invoiceNumber: string;
dueDate: string;
}) {
// Retrieve bearer token for this customer
const { bearer_token } = await getStoredCredentials(customerId);

const response = await axios.post(
`${DEBITURA_API_URL}/cases`,
{
debtor: {
name: caseData.debtorName,
email: caseData.debtorEmail,
country: caseData.debtorCountry,
},
amount: {
value: caseData.amount,
currency: caseData.currency,
},
invoice_number: caseData.invoiceNumber,
due_date: caseData.dueDate,
},
{
headers: {
Authorization: `Bearer ${bearer_token}`,
'Content-Type': 'application/json',
},
}
);

return response.data;
}

// Helper functions
async function storeEncrypted(customerId: string, data: any) {
// Implement secure storage (e.g., encrypted database)
// This is a placeholder
console.log(`Storing for ${customerId}:`, data);
}

async function getStoredCredentials(customerId: string) {
// Retrieve from secure storage
// This is a placeholder
return { bearer_token: 'tok_stored_token' };
}

async function handleConflict(errorData: any) {
// Handle 409 conflict based on can_claim flag
if (errorData.details?.can_claim) {
// Implement claim logic
console.log('Client can be claimed');
}
return null;
}

Python

Python integration example:

import os
import requests
from typing import Dict, Any

DEBITURA_API_KEY = os.environ.get('DEBITURA_API_KEY')
DEBITURA_API_URL = 'https://api.debitura.com/v1'

class DebituraClient:
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = DEBITURA_API_URL
self.session = requests.Session()
self.session.headers.update({
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
})

def link_customer(self, customer_data: Dict[str, str]) -> Dict[str, Any]:
"""Link a customer to Debitura"""
payload = {
'external_id': customer_data['id'],
'company_name': customer_data['company_name'],
'country': customer_data['country'],
'email': customer_data['email'],
'onboarding_type': 'api_only'
}

try:
response = self.session.post(
f'{self.base_url}/referral-partners/clients',
json=payload
)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 409:
# Handle conflict
return self.handle_conflict(e.response.json())
raise

def create_case(self, bearer_token: str, case_data: Dict[str, Any]) -> Dict[str, Any]:
"""Create a debt collection case"""
payload = {
'debtor': {
'name': case_data['debtor_name'],
'email': case_data['debtor_email'],
'country': case_data['debtor_country']
},
'amount': {
'value': case_data['amount'],
'currency': case_data['currency']
},
'invoice_number': case_data['invoice_number'],
'due_date': case_data['due_date']
}

# Use customer's bearer token
headers = {'Authorization': f'Bearer {bearer_token}'}
response = requests.post(
f'{self.base_url}/cases',
json=payload,
headers=headers
)
response.raise_for_status()
return response.json()

def handle_conflict(self, error_data: Dict[str, Any]) -> Dict[str, Any]:
"""Handle 409 conflict"""
details = error_data.get('error', {}).get('details', {})
if details.get('can_claim'):
print(f"Client can be claimed: {details.get('client_id')}")
# Implement claim logic
return None

# Usage
client = DebituraClient(DEBITURA_API_KEY)

# Link customer
linked_client = client.link_customer({
'id': 'cust_12345',
'company_name': 'Acme Corporation',
'country': 'DK',
'email': 'contact@acme.com'
})

# Create case
case = client.create_case(
bearer_token=linked_client['bearer_token'],
case_data={
'debtor_name': 'John Doe',
'debtor_email': 'john@example.com',
'debtor_country': 'DK',
'amount': 5000,
'currency': 'DKK',
'invoice_number': 'INV-001',
'due_date': '2024-01-01'
}
)

PHP

PHP integration example:

<?php

class DebituraClient {
private $apiKey;
private $baseUrl = 'https://api.debitura.com/v1';

public function __construct($apiKey) {
$this->apiKey = $apiKey;
}

public function linkCustomer($customerData) {
$url = $this->baseUrl . '/referral-partners/clients';

$payload = [
'external_id' => $customerData['id'],
'company_name' => $customerData['company_name'],
'country' => $customerData['country'],
'email' => $customerData['email'],
'onboarding_type' => 'api_only'
];

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $this->apiKey,
'Content-Type: application/json'
]);

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

if ($httpCode === 409) {
// Handle conflict
return $this->handleConflict(json_decode($response, true));
}

if ($httpCode >= 400) {
throw new Exception("API Error: " . $response);
}

return json_decode($response, true);
}

public function createCase($bearerToken, $caseData) {
$url = $this->baseUrl . '/cases';

$payload = [
'debtor' => [
'name' => $caseData['debtor_name'],
'email' => $caseData['debtor_email'],
'country' => $caseData['debtor_country']
],
'amount' => [
'value' => $caseData['amount'],
'currency' => $caseData['currency']
],
'invoice_number' => $caseData['invoice_number'],
'due_date' => $caseData['due_date']
];

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $bearerToken,
'Content-Type: application/json'
]);

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

if ($httpCode >= 400) {
throw new Exception("API Error: " . $response);
}

return json_decode($response, true);
}

private function handleConflict($errorData) {
$details = $errorData['error']['details'] ?? [];
if ($details['can_claim'] ?? false) {
// Implement claim logic
error_log("Client can be claimed: " . $details['client_id']);
}
return null;
}
}

// Usage
$client = new DebituraClient(getenv('DEBITURA_API_KEY'));

$linkedClient = $client->linkCustomer([
'id' => 'cust_12345',
'company_name' => 'Acme Corporation',
'country' => 'DK',
'email' => 'contact@acme.com'
]);

$case = $client->createCase(
$linkedClient['bearer_token'],
[
'debtor_name' => 'John Doe',
'debtor_email' => 'john@example.com',
'debtor_country' => 'DK',
'amount' => 5000,
'currency' => 'DKK',
'invoice_number' => 'INV-001',
'due_date' => '2024-01-01'
]
);

Common Integration Patterns

Pattern 1: Just-in-Time Client Linking

Link customers only when they first use debt collection:

async function ensureClientLinked(customerId: string) {
// Check if already linked
let credentials = await db.getDebituraCredentials(customerId);

if (!credentials) {
// Get customer data
const customer = await db.getCustomer(customerId);

// Link to Debitura
const result = await linkCustomer({
id: customer.id,
companyName: customer.company_name,
country: customer.country,
email: customer.email,
});

// Store credentials
await db.storeDebituraCredentials(customerId, {
client_id: result.client_id,
bearer_token: result.bearer_token,
});

credentials = result;
}

return credentials.bearer_token;
}

// Usage in your case creation endpoint
app.post('/api/debt-collection/cases', async (req, res) => {
const customerId = req.user.id;
const caseData = req.body;

// Ensure customer is linked (happens automatically if needed)
const bearerToken = await ensureClientLinked(customerId);

// Create case using bearer token
const case = await createCase(bearerToken, caseData);

res.json(case);
});

Pattern 2: Webhook Handler

Handle Debitura webhooks:

import crypto from 'crypto';

// Verify webhook signature
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload);
const digest = hmac.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(digest)
);
}

// Webhook endpoint
app.post('/webhooks/debitura', async (req, res) => {
const signature = req.headers['x-debitura-signature'] as string;
const payload = JSON.stringify(req.body);

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

const { event, data } = req.body;

try {
switch (event) {
case 'client.onboarding_completed':
await handleOnboardingCompleted(data);
break;

case 'case.status_changed':
await handleCaseStatusChange(data);
break;

case 'case.payment_received':
await handlePaymentReceived(data);
break;

default:
console.log(`Unhandled event: ${event}`);
}

res.json({ received: true });
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).json({ error: 'Processing failed' });
}
});

async function handleOnboardingCompleted(data: any) {
const { external_id, client_id, bearer_token } = data;

// Store credentials
await db.storeDebituraCredentials(external_id, {
client_id,
bearer_token,
});

// Notify customer
await sendEmail(external_id, {
template: 'onboarding_complete',
subject: 'Debt collection is ready!',
});
}

async function handleCaseStatusChange(data: any) {
const { case_id, status, external_reference } = data;

// Update your database
await db.updateCaseStatus(external_reference, status);

// Notify customer
await notifyCustomer(external_reference, {
message: `Case status updated to: ${status}`,
});
}

async function handlePaymentReceived(data: any) {
const { case_id, amount, external_reference } = data;

// Update your records
await db.recordPayment(external_reference, amount);

// Celebrate with customer
await sendEmail(external_reference, {
template: 'payment_received',
data: { amount },
});
}

Pattern 3: White-Label Onboarding Flow

Implement white-label onboarding:

// 1. Create client with white-label onboarding
app.post('/api/onboarding/start', async (req, res) => {
const customerId = req.user.id;
const customer = await db.getCustomer(customerId);

const result = await axios.post(
`${DEBITURA_API_URL}/referral-partners/clients`,
{
external_id: customerId,
company_name: customer.company_name,
country: customer.country,
email: customer.email,
onboarding_type: 'white_label',
onboarding_metadata: {
language: customer.language,
prefill: {
vat_number: customer.vat_number,
phone: customer.phone,
},
},
},
{
headers: {
Authorization: `Bearer ${DEBITURA_API_KEY}`,
'Content-Type': 'application/json',
},
}
);

// Store client_id for tracking
await db.updateCustomer(customerId, {
debitura_client_id: result.data.client_id,
onboarding_status: 'pending',
});

// Return onboarding URL to frontend
res.json({
onboarding_url: result.data.onboarding_url,
expires_at: result.data.onboarding_expires_at,
});
});

// 2. Handle return from onboarding
app.get('/onboarding/complete', async (req, res) => {
const { client_id, status } = req.query;

if (status === 'completed') {
// Update database
await db.updateCustomerByClientId(client_id, {
onboarding_status: 'completed',
});

// Show success page
res.render('onboarding-success');
} else if (status === 'cancelled') {
res.render('onboarding-cancelled');
}
});

// 3. Handle completion webhook (to get bearer token)
async function handleOnboardingCompleted(data: any) {
const { external_id, bearer_token } = data;

await db.storeDebituraCredentials(external_id, {
bearer_token: await encrypt(bearer_token),
onboarding_completed_at: new Date(),
});
}

Pattern 4: Revenue Tracking Dashboard

Build a revenue dashboard:

app.get('/api/dashboard/revenue', async (req, res) => {
const response = await axios.get(
`${DEBITURA_API_URL}/referral-partners/revenue/dashboard`,
{
headers: {
Authorization: `Bearer ${DEBITURA_API_KEY}`,
},
}
);

const dashboard = response.data;

res.json({
current_month: {
commission: dashboard.summary.current_month.total_commission,
cases: dashboard.summary.current_month.cases_attributed,
average_per_case:
dashboard.summary.current_month.average_commission_per_case,
},
year_to_date: {
commission: dashboard.summary.year_to_date.total_commission,
cases: dashboard.summary.year_to_date.cases_attributed,
},
pending_payment: dashboard.pending_payment,
});
});

// Get revenue trend
app.get('/api/dashboard/revenue/trend', async (req, res) => {
const response = await axios.get(
`${DEBITURA_API_URL}/referral-partners/revenue/periods`,
{
params: {
start: '2024-01',
end: '2024-12',
},
headers: {
Authorization: `Bearer ${DEBITURA_API_KEY}`,
},
}
);

// Format for charting library
const chartData = response.data.periods.map((period: any) => ({
month: period.period,
commission: period.total_commission.value,
cases: period.cases_created,
}));

res.json(chartData);
});

Testing Examples

Unit Test Example (Jest)

import { linkCustomer, createCase } from './debitura-client';
import axios from 'axios';

jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;

describe('Debitura Integration', () => {
beforeEach(() => {
jest.clearAllMocks();
});

test('should link customer successfully', async () => {
mockedAxios.post.mockResolvedValue({
data: {
client_id: 'cli_test123',
bearer_token: 'tok_test789',
status: 'active',
},
});

const result = await linkCustomer({
id: 'cust_123',
companyName: 'Test Corp',
country: 'DK',
email: 'test@example.com',
});

expect(result.client_id).toBe('cli_test123');
expect(result.bearer_token).toBe('tok_test789');
expect(mockedAxios.post).toHaveBeenCalledWith(
expect.stringContaining('/referral-partners/clients'),
expect.objectContaining({
external_id: 'cust_123',
company_name: 'Test Corp',
}),
expect.any(Object)
);
});

test('should handle 409 conflict', async () => {
mockedAxios.post.mockRejectedValue({
response: {
status: 409,
data: {
error: {
type: 'conflict_error',
details: { can_claim: false },
},
},
},
});

await expect(
linkCustomer({
id: 'cust_existing',
companyName: 'Test Corp',
country: 'DK',
email: 'test@example.com',
})
).rejects.toThrow();
});
});

Next Steps