Skip to content

Latest commit

 

History

History
299 lines (236 loc) · 7.6 KB

File metadata and controls

299 lines (236 loc) · 7.6 KB

Webhook Integration Guide

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.


Table of Contents


Creating a Subscription

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.

Managing Subscriptions

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

Supported Event Types

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

Payload Examples

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": { }
}

loan_approved

{
  "event": "loan_approved",
  "id": "evt_loan_42",
  "timestamp": "2026-05-28T12:00:00.000Z",
  "data": {
    "loanId": 42,
    "borrower": "GABCDEF...",
    "amount": "5000",
    "termMonths": 12
  }
}

repayment_confirmed

{
  "event": "repayment_confirmed",
  "id": "evt_repay_99",
  "timestamp": "2026-05-28T12:05:00.000Z",
  "data": {
    "loanId": 42,
    "borrower": "GABCDEF...",
    "amount": "450",
    "txHash": "a1b2c3d4..."
  }
}

loan_defaulted

{
  "event": "loan_defaulted",
  "id": "evt_default_7",
  "timestamp": "2026-05-28T12:10:00.000Z",
  "data": {
    "loanId": 42,
    "borrower": "GABCDEF...",
    "outstandingAmount": "3200"
  }
}

loan_liquidated

{
  "event": "loan_liquidated",
  "id": "evt_liq_3",
  "timestamp": "2026-05-28T12:15:00.000Z",
  "data": {
    "loanId": 42,
    "borrower": "GABCDEF...",
    "collateralSeized": true,
    "borrowerRefund": "150"
  }
}

repayment_due

{
  "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"
  }
}

score_changed

{
  "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 & Retry Semantics

  1. Delivery method: HTTP POST to the subscriber URL.
  2. Timeout: The endpoint must respond within 10 seconds.
  3. 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
  4. Max attempts: 8 total (1 initial + 7 retries).
  5. Delivery window: Events older than 24 hours are not retried.
  6. Ordering: Webhooks are delivered on a best-effort basis and may not arrive in the exact order events occurred.

Circuit Breaker

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/:id with { "active": true }.

Verifying HMAC Signatures

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 generated
  • v1 — Hexadecimal HMAC-SHA256 digest

Verification snippet (Node.js)

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 use timingSafeEqual (or your language's constant-time comparison) when verifying the signature to prevent timing attacks.

Obtaining your secret

The signing secret is the WEBHOOK_SIGNING_SECRET environment variable configured on the RemitLend server. Contact the RemitLend team to obtain your shared secret.


Subscriber Response Requirements

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.

Need Help?

Contact the RemitLend team or open an issue on GitHub for integration support.