RemitLend can deliver real-time event notifications to external services via webhooks. This guide covers everything an external integrator needs to subscribe, receive, and verify webhook deliveries.
- Creating a Subscription
- Supported Event Types
- Payload Examples
- Delivery & Retry Semantics
- Circuit Breaker
- Verifying HMAC Signatures
- Subscriber Response Requirements
Endpoint: POST /api/webhooks/subscriptions
Headers:
Content-Type: application/json
Authorization: Bearer <your-jwt-token>
Request body:
{
"url": "https://your-service.com/webhooks/remitlend",
"events": ["loan_approved", "repayment_confirmed", "loan_defaulted"],
"description": "My loan tracking service (optional)"
}| Field | Type | Description |
|---|---|---|
url |
string | HTTPS endpoint that will receive POST requests |
events |
string[] | Array of event types |
description |
string | Optional human-readable label |
Response (201):
{
"success": true,
"data": {
"id": "sub_abc123",
"url": "https://your-service.com/webhooks/remitlend",
"events": ["loan_approved", "repayment_confirmed", "loan_defaulted"],
"active": true,
"createdAt": "2026-05-28T12:00:00.000Z"
}
}After creation the subscription is immediately active. No verification handshake is required.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/webhooks/subscriptions |
List all subscriptions |
| GET | /api/webhooks/subscriptions/:id |
Get a single subscription |
| PUT | /api/webhooks/subscriptions/:id |
Update events / URL |
| DELETE | /api/webhooks/subscriptions/:id |
Delete a subscription |
| Event | Description |
|---|---|
loan_approved |
A borrower's loan has been approved |
repayment_due |
A repayment is coming due soon |
repayment_confirmed |
A repayment was received and confirmed |
loan_defaulted |
A loan has been marked as defaulted |
loan_liquidated |
Collateral has been liquidated after default |
score_changed |
A borrower's credit score changed |
Every delivery is a JSON POST with the following envelope:
{
"event": "<event_type>",
"id": "<unique_delivery_id>",
"timestamp": "2026-05-28T12:00:00.000Z",
"data": { }
}{
"event": "loan_approved",
"id": "evt_loan_42",
"timestamp": "2026-05-28T12:00:00.000Z",
"data": {
"loanId": 42,
"borrower": "GABCDEF...",
"amount": "5000",
"termMonths": 12
}
}{
"event": "repayment_confirmed",
"id": "evt_repay_99",
"timestamp": "2026-05-28T12:05:00.000Z",
"data": {
"loanId": 42,
"borrower": "GABCDEF...",
"amount": "450",
"txHash": "a1b2c3d4..."
}
}{
"event": "loan_defaulted",
"id": "evt_default_7",
"timestamp": "2026-05-28T12:10:00.000Z",
"data": {
"loanId": 42,
"borrower": "GABCDEF...",
"outstandingAmount": "3200"
}
}{
"event": "loan_liquidated",
"id": "evt_liq_3",
"timestamp": "2026-05-28T12:15:00.000Z",
"data": {
"loanId": 42,
"borrower": "GABCDEF...",
"collateralSeized": true,
"borrowerRefund": "150"
}
}{
"event": "repayment_due",
"id": "evt_due_21",
"timestamp": "2026-05-28T12:00:00.000Z",
"data": {
"loanId": 42,
"borrower": "GABCDEF...",
"dueDate": "2026-06-01",
"amount": "450"
}
}{
"event": "score_changed",
"id": "evt_score_15",
"timestamp": "2026-05-28T12:00:00.000Z",
"data": {
"userId": "GABCDEF...",
"previousScore": 650,
"newScore": 665,
"reason": "on-time repayment"
}
}- Delivery method: HTTP POST to the subscriber URL.
- Timeout: The endpoint must respond within 10 seconds.
- Retry policy: Deliveries are retried with exponential backoff:
- Retry 1: 10 seconds
- Retry 2: 30 seconds
- Retry 3: 1 minute
- Retry 4: 5 minutes
- Retry 5: 15 minutes
- Retry 6: 30 minutes
- Retry 7: 1 hour
- Max attempts: 8 total (1 initial + 7 retries).
- Delivery window: Events older than 24 hours are not retried.
- Ordering: Webhooks are delivered on a best-effort basis and may not arrive in the exact order events occurred.
If a subscriber endpoint fails to respond with a 2xx status for 5 consecutive deliveries, the subscription is automatically deactivated to avoid wasting resources.
While deactivated:
- No further events are sent to the subscriber.
- The subscription status changes to
deactivated. - You can re-activate the subscription by calling
PUT /api/webhooks/subscriptions/:idwith{ "active": true }.
Each delivery includes an X-RemitLend-Signature header containing an
HMAC-SHA256 signature of the raw request body.
Header format:
X-RemitLend-Signature: t=1745827200,v1=abc123def456...
t— Unix timestamp of when the signature was generatedv1— Hexadecimal HMAC-SHA256 digest
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyWebhookSignature(
body: string,
signatureHeader: string,
secret: string,
): boolean {
const parts = signatureHeader.split(",");
const timestamp = parts.find((p) => p.startsWith("t="))?.slice(2);
const digest = parts.find((p) => p.startsWith("v1="))?.slice(3);
if (!timestamp || !digest) return false;
const payload = `${timestamp}.${body}`;
const expected = createHmac("sha256", secret)
.update(payload)
.digest("hex");
if (expected.length !== digest.length) return false;
return timingSafeEqual(Buffer.from(expected), Buffer.from(digest));
}
⚠️ Important: Always usetimingSafeEqual(or your language's constant-time comparison) when verifying the signature to prevent timing attacks.
The signing secret is the WEBHOOK_SIGNING_SECRET environment variable
configured on the RemitLend server. Contact the RemitLend team to obtain
your shared secret.
| Code | Meaning |
|---|---|
| 2xx | Delivery accepted — no retry |
| 4xx | Request rejected — permanent failure (no retry) |
| 5xx | Server error — will be retried |
| Timeout | Treated as a failure — will be retried |
- Respond within 10 seconds. Slow responses are counted as failures.
- Returning any 2xx status (200, 201, 202, 204) acknowledges delivery.
Contact the RemitLend team or open an issue on GitHub for integration support.