Skip to main content

Integration Examples

Real-world integration patterns and code examples for implementing the Debitura Customer API in your applications. This guide provides complete, production-ready examples for common use cases.

Overview

This page contains practical integration examples for various platforms and technologies. Each example includes complete code with error handling, authentication, and best practices.

Platform Examples

Node.js / Express Integration

Complete Node.js integration with Express framework:

// debitura-client.js
const fetch = require('node-fetch');

class DebituraClient {
constructor(apiKey, apiSecret, baseUrl = 'https://api.debitura.com') {
this.apiKey = apiKey;
this.apiSecret = apiSecret;
this.baseUrl = baseUrl;
this.accessToken = null;
this.tokenExpiry = null;
}

async authenticate() {
const response = await fetch(`${this.baseUrl}/api/auth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
apiKey: this.apiKey,
apiSecret: this.apiSecret
})
});

if (!response.ok) {
throw new Error(`Authentication failed: ${response.statusText}`);
}

const data = await response.json();
this.accessToken = data.accessToken;
this.tokenExpiry = Date.now() + (data.expiresIn * 1000);

return data.accessToken;
}

async ensureAuthenticated() {
if (!this.accessToken || Date.now() >= this.tokenExpiry) {
await this.authenticate();
}
}

async request(method, endpoint, body = null) {
await this.ensureAuthenticated();

const options = {
method,
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json'
}
};

if (body) {
options.body = JSON.stringify(body);
}

const response = await fetch(`${this.baseUrl}${endpoint}`, options);

if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.message || response.statusText}`);
}

return response.json();
}

async createCase(caseData) {
return this.request('POST', '/api/cases', caseData);
}

async getCase(caseId) {
return this.request('GET', `/api/cases/${caseId}`);
}

async getCases(filters = {}) {
const query = new URLSearchParams(filters).toString();
return this.request('GET', `/api/cases?${query}`);
}

async recordPayment(caseId, paymentData) {
return this.request('POST', `/api/cases/${caseId}/payments`, paymentData);
}
}

// Usage example
const client = new DebituraClient(
process.env.DEBITURA_API_KEY,
process.env.DEBITURA_API_SECRET
);

// Express route handlers
app.post('/api/cases/create', async (req, res) => {
try {
const caseData = {
debtorName: req.body.debtorName,
debtorCvr: req.body.debtorCvr,
debtorEmail: req.body.debtorEmail,
principalAmount: req.body.amount,
currency: 'DKK',
invoiceNumber: req.body.invoiceNumber,
dueDate: req.body.dueDate,
referenceNumber: req.body.referenceNumber
};

const result = await client.createCase(caseData);
res.json(result);
} catch (error) {
console.error('Case creation failed:', error);
res.status(500).json({ error: error.message });
}
});

module.exports = DebituraClient;

C# / .NET Integration

Complete C# integration with dependency injection:

using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;

public class DebituraClient
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly string _apiSecret;
private string _accessToken;
private DateTime _tokenExpiry;

public DebituraClient(HttpClient httpClient, IConfiguration configuration)
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri(configuration["Debitura:BaseUrl"]);
_apiKey = configuration["Debitura:ApiKey"];
_apiSecret = configuration["Debitura:ApiSecret"];
}

private async Task EnsureAuthenticatedAsync()
{
if (string.IsNullOrEmpty(_accessToken) || DateTime.UtcNow >= _tokenExpiry)
{
await AuthenticateAsync();
}
}

private async Task AuthenticateAsync()
{
var request = new
{
apiKey = _apiKey,
apiSecret = _apiSecret
};

var response = await _httpClient.PostAsJsonAsync("/api/auth/token", request);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadFromJsonAsync<AuthResponse>();
_accessToken = result.AccessToken;
_tokenExpiry = DateTime.UtcNow.AddSeconds(result.ExpiresIn - 60);

_httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _accessToken);
}

public async Task<CaseResponse> CreateCaseAsync(CaseRequest request)
{
await EnsureAuthenticatedAsync();

var response = await _httpClient.PostAsJsonAsync("/api/cases", request);
response.EnsureSuccessStatusCode();

return await response.Content.ReadFromJsonAsync<CaseResponse>();
}

public async Task<CaseDetails> GetCaseAsync(string caseId)
{
await EnsureAuthenticatedAsync();

var response = await _httpClient.GetAsync($"/api/cases/{caseId}");
response.EnsureSuccessStatusCode();

return await response.Content.ReadFromJsonAsync<CaseDetails>();
}

public async Task<PaymentResponse> RecordPaymentAsync(
string caseId,
PaymentRequest payment)
{
await EnsureAuthenticatedAsync();

var response = await _httpClient.PostAsJsonAsync(
$"/api/cases/{caseId}/payments",
payment
);
response.EnsureSuccessStatusCode();

return await response.Content.ReadFromJsonAsync<PaymentResponse>();
}
}

// Startup.cs configuration
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<DebituraClient>();
services.AddControllers();
}

// Example controller
[ApiController]
[Route("api/[controller]")]
public class CasesController : ControllerBase
{
private readonly DebituraClient _debituraClient;

public CasesController(DebituraClient debituraClient)
{
_debituraClient = debituraClient;
}

[HttpPost]
public async Task<IActionResult> CreateCase([FromBody] CaseRequest request)
{
try
{
var result = await _debituraClient.CreateCaseAsync(request);
return Ok(result);
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
}
}
}

Python / Django Integration

Complete Python integration with Django:

# debitura_client.py
import requests
from datetime import datetime, timedelta
from django.conf import settings

class DebituraClient:
def __init__(self):
self.base_url = settings.DEBITURA_BASE_URL
self.api_key = settings.DEBITURA_API_KEY
self.api_secret = settings.DEBITURA_API_SECRET
self.access_token = None
self.token_expiry = None

def authenticate(self):
"""Authenticate and get access token"""
response = requests.post(
f"{self.base_url}/api/auth/token",
json={
"apiKey": self.api_key,
"apiSecret": self.api_secret
}
)
response.raise_for_status()

data = response.json()
self.access_token = data['accessToken']
self.token_expiry = datetime.now() + timedelta(seconds=data['expiresIn'])

return self.access_token

def ensure_authenticated(self):
"""Ensure we have a valid access token"""
if not self.access_token or datetime.now() >= self.token_expiry:
self.authenticate()

def request(self, method, endpoint, data=None):
"""Make authenticated API request"""
self.ensure_authenticated()

headers = {
'Authorization': f'Bearer {self.access_token}',
'Content-Type': 'application/json'
}

response = requests.request(
method,
f"{self.base_url}{endpoint}",
json=data,
headers=headers
)
response.raise_for_status()

return response.json()

def create_case(self, case_data):
"""Create a new case"""
return self.request('POST', '/api/cases', case_data)

def get_case(self, case_id):
"""Get case details"""
return self.request('GET', f'/api/cases/{case_id}')

def record_payment(self, case_id, payment_data):
"""Record a payment"""
return self.request(
'POST',
f'/api/cases/{case_id}/payments',
payment_data
)

# views.py
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_exempt
import json

from .debitura_client import DebituraClient

@csrf_exempt
@require_http_methods(["POST"])
def create_case(request):
"""Create a new debt collection case"""
try:
data = json.loads(request.body)

client = DebituraClient()
result = client.create_case({
'debtorName': data['debtorName'],
'debtorCvr': data['debtorCvr'],
'debtorEmail': data['debtorEmail'],
'principalAmount': data['amount'],
'currency': 'DKK',
'invoiceNumber': data['invoiceNumber'],
'dueDate': data['dueDate'],
'referenceNumber': data['referenceNumber']
})

return JsonResponse(result)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)

Integration Patterns

ERP Integration

Automated case creation from overdue invoices:

// ERP integration example - automatic case creation
async function processOverdueInvoices() {
// 1. Query ERP for overdue invoices
const overdueInvoices = await erpSystem.getOverdueInvoices({
daysOverdue: 30,
minimumAmount: 1000
});

// 2. Filter invoices not already in collection
const casesToCreate = [];

for (const invoice of overdueInvoices) {
const existingCase = await debituraClient.getCases({
referenceNumber: invoice.invoiceNumber
});

if (existingCase.cases.length === 0) {
casesToCreate.push({
debtorName: invoice.customerName,
debtorCvr: invoice.customerCvr,
debtorEmail: invoice.customerEmail,
debtorPhone: invoice.customerPhone,
principalAmount: invoice.totalAmount,
currency: invoice.currency,
invoiceNumber: invoice.invoiceNumber,
dueDate: invoice.dueDate,
referenceNumber: invoice.invoiceNumber,
divisionId: invoice.departmentId
});
}
}

// 3. Create cases in batch
const results = [];
for (const caseData of casesToCreate) {
try {
const result = await debituraClient.createCase(caseData);
results.push({ success: true, case: result });

// Update ERP with case reference
await erpSystem.updateInvoice(caseData.invoiceNumber, {
collectionCaseId: result.caseId,
collectionStatus: 'In Collection'
});
} catch (error) {
results.push({
success: false,
invoice: caseData.invoiceNumber,
error: error.message
});
}
}

return results;
}

// Schedule daily processing
const cron = require('node-cron');
cron.schedule('0 9 * * *', processOverdueInvoices);

Webhook Handler Implementation

Complete webhook handler with signature verification:

// webhook-handler.js
const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

// Verify webhook signature
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}

// Webhook endpoint
app.post('/webhooks/debitura', async (req, res) => {
const signature = req.headers['x-debitura-signature'];
const webhookSecret = process.env.DEBITURA_WEBHOOK_SECRET;

// Verify signature
if (!verifyWebhookSignature(req.body, signature, webhookSecret)) {
return res.status(401).send('Invalid signature');
}

// Acknowledge immediately
res.status(200).send('OK');

// Process event asynchronously
processWebhookEvent(req.body).catch(error => {
console.error('Webhook processing error:', error);
});
});

async function processWebhookEvent(event) {
switch (event.eventType) {
case 'case.status.changed':
await handleStatusChange(event.data);
break;

case 'payment.received':
await handlePaymentReceived(event.data);
break;

case 'case.resolved':
await handleCaseResolved(event.data);
break;

default:
console.log(`Unknown event type: ${event.eventType}`);
}
}

async function handleStatusChange(data) {
// Update internal system
await database.updateCaseStatus(data.caseId, data.newStatus);

// Notify relevant users
if (data.newStatus === 'In Collection') {
await notificationService.notify({
type: 'case_escalated',
caseId: data.caseId,
caseNumber: data.caseNumber
});
}
}

async function handlePaymentReceived(data) {
// Update accounting system
await accountingSystem.recordPayment({
invoiceNumber: data.caseNumber,
amount: data.amount,
date: data.paymentDate,
reference: data.paymentId
});

// Send confirmation email
await emailService.send({
to: 'accounting@example.com',
subject: `Payment received: ${data.caseNumber}`,
body: `Payment of ${data.amount} ${data.currency} received.`
});
}

async function handleCaseResolved(data) {
// Close case in internal system
await database.closeCase(data.caseId, data.resolution);

// Generate report
await reportingService.generateCaseReport(data.caseId);
}

app.listen(3000, () => {
console.log('Webhook handler listening on port 3000');
});

Payment Reconciliation

Automated bank reconciliation:

// BankReconciliationService.cs
public class BankReconciliationService
{
private readonly DebituraClient _debituraClient;
private readonly IBankService _bankService;
private readonly ILogger<BankReconciliationService> _logger;

public async Task<ReconciliationResult> ReconcileTransactions(
DateTime from,
DateTime to)
{
// 1. Fetch bank transactions
var bankTransactions = await _bankService.GetTransactionsAsync(from, to);

// 2. Attempt automatic matching
var matchRequest = new
{
bankTransactions = bankTransactions.Select(t => new
{
transactionId = t.Id,
amount = t.Amount,
date = t.Date,
reference = t.Reference,
accountNumber = t.AccountNumber
})
};

var matchResult = await _debituraClient.MatchTransactionsAsync(matchRequest);

// 3. Process matched transactions
foreach (var match in matchResult.Matched)
{
_logger.LogInformation(
"Matched transaction {TransactionId} to case {CaseNumber}",
match.TransactionId,
match.CaseNumber
);

// Update internal records
await UpdatePaymentRecordAsync(match);
}

// 4. Handle unmatched transactions
if (matchResult.Unmatched.Any())
{
await NotifyUnmatchedTransactionsAsync(matchResult.Unmatched);
}

return new ReconciliationResult
{
TotalTransactions = bankTransactions.Count,
Matched = matchResult.Matched.Count,
Unmatched = matchResult.Unmatched.Count
};
}
}

Best Practices

Error Handling

Implement robust error handling:

async function robustCaseCreation(caseData) {
const maxRetries = 3;
let lastError;

for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await debituraClient.createCase(caseData);
} catch (error) {
lastError = error;

// Don't retry client errors (4xx)
if (error.statusCode >= 400 && error.statusCode < 500) {
throw error;
}

// Exponential backoff
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));

console.log(`Retry attempt ${attempt} after ${delay}ms`);
}
}

throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`);
}

Logging and Monitoring

Comprehensive logging:

public class DebituraClientWithLogging : DebituraClient
{
private readonly ILogger _logger;

public override async Task<CaseResponse> CreateCaseAsync(CaseRequest request)
{
var correlationId = Guid.NewGuid().ToString();

_logger.LogInformation(
"Creating case {CorrelationId}: Debtor={DebtorName}, Amount={Amount}",
correlationId,
request.DebtorName,
request.PrincipalAmount
);

var stopwatch = Stopwatch.StartNew();

try
{
var result = await base.CreateCaseAsync(request);

_logger.LogInformation(
"Case created {CorrelationId}: CaseId={CaseId}, Duration={Duration}ms",
correlationId,
result.CaseId,
stopwatch.ElapsedMilliseconds
);

return result;
}
catch (Exception ex)
{
_logger.LogError(
ex,
"Case creation failed {CorrelationId}: Duration={Duration}ms",
correlationId,
stopwatch.ElapsedMilliseconds
);

throw;
}
}
}

Testing

Unit Testing

Example unit tests:

// debitura-client.test.js
const DebituraClient = require('./debitura-client');

describe('DebituraClient', () => {
let client;

beforeEach(() => {
client = new DebituraClient('test-key', 'test-secret');
});

test('should authenticate successfully', async () => {
const token = await client.authenticate();
expect(token).toBeDefined();
expect(client.accessToken).toBeTruthy();
});

test('should create case with valid data', async () => {
const caseData = {
debtorName: 'Test Company ApS',
debtorCvr: '12345678',
principalAmount: 10000,
currency: 'DKK',
invoiceNumber: 'TEST-001'
};

const result = await client.createCase(caseData);
expect(result.caseId).toBeDefined();
expect(result.status).toBe('Created');
});

test('should handle authentication errors', async () => {
const invalidClient = new DebituraClient('invalid', 'invalid');

await expect(invalidClient.authenticate()).rejects.toThrow();
});
});

All code examples are provided as-is for reference. Adapt them to your specific requirements and security policies.