Webhooks

Webhooks are used to notify your platform about events on finmid side. finmid sends these notifications to an endpoint hosted in your environment configured to receive and process them.

What is a webhook?

A webhook is an asynchronous notification in the shape of an HTTP request. Webhooks are a well known practice used by many services. We use webhooks to notify your platform asynchronously about events on finmid side (e.g. a buyer has been activated). This allows you to react to certain events and avoids the need for polling.

How to start receiving webhooks?

To start receiving webhooks, an endpoint has to be configured on both your and finmid ends. You need to provide a URL to which webhooks will be sent. This must be an HTTPS URL that is under your control or trusted by you. Additionally, we need certain security parameters from you to protect your endpoint. There are two ways to configure a webhook endpoint on finmid's end.

πŸ“˜

Option A: Setting up an endpoint via customer support

Please contact [email protected] and provide the following data points:

  • url β€” URL of your webhook endpoint (only HTTPS URLs)
  • basic_auth_username β€” HTTP basic authentication username, for webhook authentication
  • basic_auth_password β€” HTTP basic authentication password, for webhook authentication
  • signature_secret β€” HMAC signature secret, for verifying the webhook payload

πŸ“˜

Option B: Setting up an endpoint via API

You can also use our API to create a new webhook endpoint. This requires an API key. Please contact our customer support if you don't have an API at hand.

curl --request POST \
     --url https://api.finmid.com/api/v2/b2b-payments/webhook-endpoints \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'X-API-KEY: your_apikey' \
     --data '
{
     "url": "https://example.com/your-endpoint.php",
     "auth_username": "user123",
     "auth_password": "p455w0rd",
     "signature_secret": "s3cr3t"
}
'

πŸ’‘

You can configure multiple webhook endpoints: each of them will be receiving webhook notifications.

Protecting you webhook endpoint

Webhooks will be sent to your endpoint over the public internet. Therefore we only support HTTPS endpoint URLs. Custom self-signed certificates are not supported. We provide two important security features which will help you to protect your endpoint:

⚠

It is strongly recommended to utilize the above security features to protect your endpoint.

Webhook authentication

Webhooks utilize the HTTP basic authentication method. This method has very good support and is easy to implement while providing a sufficient level of protection.

This is just one example how easy the implementation is for an Express.js based app:

const app = require("express")();
const basicAuth = require("express-basic-auth");

app.use(
    basicAuth({
        users: { admin: "supersecret" },
    })
);

Payload verification

All webhooks provide a X-Payload-Signature header. It contains a HMAC-SHA-256 signature of the payload which was generated using the signature secret of the webhook endpoint. Please note that the header value is encoded as Base64.

You can check the signature like so:

  1. Read the signature from HTTP header X-Payload-Signature
  2. Base64-decode the signature value (if necessary)
  3. Generate your signature from the webhook payload using signature_secret
  4. Compare signature from webhook header with your generated signature. The webhook is verified if signatures match.

Pseudo code example of the above steps:

var signature = request.header("X-Payload-Signature");
var payload = request.body();

// Assume generatedSignature is a Base64-encoded signature value already
var generatedSignature = HMAC.createSha256(payload, env.SIGNATURE_SECRET);

if (signature === generatedSignature) {
    // Webhook verified
}

Additional security tips

Your webhook endpoint already has quite good protection if you have implemented the above security features. However, you might consider the following additional tips:

  • Choose a random, hard to guess webhook endpoint URL. This will make it hard for attackers to identify your webhook endpoint.
  • Don't expose sensitive error information with your webhook endpoint. Make sure to log errors internally and only return HTTP 404 Not Found in case of an error. Even if an attacker identifies your webhook endpoint, they might get distracted by the not found error and stop their efforts.

Webhook Delivery Guarantees

In the event of an error when posting webhooks to your endpoint, finmid will attempt to deliver your webhooks for up to one day, with exponential backoff.

Webhook Payload Structure

This is a breakdown as to how finmid’s webhooks are structured, at a global level, and event-specific level.

General Webhook Payload

The webhook payload might contain multiple events. This allows us to reduce HTTP traffic.
Our webhooks all follow the same general payload structure, which can be found below.

Fields labelled as event specific are detailed in the next section.

{
  "webhook_id": "e8cce989-0c48-4036-9bf2-1f0206398743",
  "timestamp": "2022-02-10T13:48:48.892708722Z",
  "events": [
    {
      "event_id": "ebda8288-8889-45e8-b672-0741e3553754",
      "timestamp": "2022-02-10T13:42:35.282908722Z",
      "type": // Event Specific,
      "data": {
        // Event Specific
      }
    },
    {
      "event_id": "4907a0f7-b53f-4fe7-90bd-bded64a55707",
      // ...
    }
  ]
}

Generic Fields

FieldTypeDescription
webhook_idstring
timestampstringCorresponds to when the webhook was created.
eventslist of event objects

Event Object Fields

FieldTypeDescription
event_idstring
timestampstringCorresponds to when the event occured, and should be used as the source of truth to reconstruct event timelines.
typestringType of the event
dataobjectEvent-related data

Types of Events & Payloads

This is a list of all the types of events we currently send. Do be aware that new events may be added.

Events are distinguished by the β€œtype” field in the payload, and additional data is provided in the β€œdata” field.

Webhooks related to buyer / seller status change and payment request repayment can be tested via the simulation endpoints.

Buyer / Seller Status Changes

buyer.status_changed

This notification is sent when the status of a buyer has been asynchronously changed from one status to another.
The new_status field will correspond to the data returned when sending a GET request to our Get Buyer endpoint.

{
    "event_id": "ebda8288-8889-45e8-b672-0741e3553754",
    "timestamp": "2022-02-10T13:42:35.282908722Z",
    "type": "buyer.status_changed",
    "data": {
        "buyer_id": "your-buyer-id",
        "old_status": "PENDING",
        "new_status": "ACTIVE"
    }
}

Data Fields:

FieldTypeDescription
buyer_idstringID of the buyer.
old_statusstringPrevious buyer status. Can be: PENDING, ACTIVE, INACTIVE, REJECTED, SUSPENDED.
new_statusstringCurrent buyer status. Can be: PENDING, ACTIVE, INACTIVE, REJECTED, SUSPENDED.
seller.status_changed

This notification is sent when the status of a seller has been asynchronously changed from one status to another.
The new_status field will correspond to the data returned when sending a GET request to our Get Seller endpoint.

{
    "event_id": "ebda8288-8889-45e8-b672-0741e3553754",
    "timestamp": "2022-02-10T13:42:35.282908722Z",
    "type": "seller.status_changed",
    "data": {
        "seller_id": "your-seller-id",
        "old_status": "PENDING",
        "new_status": "ACTIVE"
    }
}

Data Fields:

FieldTypeDescription
seller_idstringID of the seller.
old_statusstringPrevious seller status. Can be: PENDING, ACTIVE, INACTIVE, REJECTED, SUSPENDED.
new_statusstringCurrent seller status. Can be: PENDING, ACTIVE, INACTIVE, REJECTED, SUSPENDED.

Payment Request - Repayment Events

payment_request.repayment.repaid

This notification is sent when a payment request has been fully repaid by the buyer, at which point the Payment Request can be considered as CLOSED.

{
    "event_id": "ebda8288-8889-45e8-b672-0741e3553754",
    "timestamp": "2022-02-10T13:42:35.282908722Z",
    "type": "payment_request.repayment.repaid",
    "data": {
        "payment_request_id": "abc-abc"
    }
}

Data Fields:

FieldTypeDescription
payment_request_idstringID of the payment request
payment_request.repayment.reminder

This webhook is sent whenever a Payment Request in PENDING_REPAYMENT status is either near, or past the repayment deadline. This will be sent regardless of whether the reminder email is sent to the buyer or not.

{
    "event_id": "ebda8288-8889-45e8-b672-0741e3553754",
    "timestamp": "2023-02-01T13:42:35.282908722Z",
    "type": "payment_request.repayment.reminder",
    "data": {
        "payment_request_id": "abc-abc",
        "due_date": "2023-02-04",
        "days_from_due_date": "-3"
    }
}

Data Fields:

FieldTypeDescription
payment_request_idstringID of the payment request
due_datestringPayment request due date, in YYYY-MM-DD format.
days_from_due_dateintegerDifference in calendar days from current date to repayment due date.