Webhooks

In this guide, we will look at how to register and consume webhooks to integrate your app with Felloh. With webhooks, your app can know when something happens in Felloh, such as someone making a payment or a refund being processed.

To register a new webhook, you need to have a URL in your app that Felloh can call. You can configure a new webhook from the Felloh dashboard under Developer Configuration. Add your URL, pick the events you want to listen for, and optionally include child organisations. A signing key is generated when the webhook is created and shown only once, so store it securely.

Now, whenever something of interest happens in your app, a webhook is fired off by Felloh. In the next section, we'll look at how to consume webhooks.


Webhook Signing

To ensure secure communication and verify the authenticity of incoming webhook requests, Felloh signs all webhook payloads using an HMAC-SHA256 signature (available when creating webhooks in dashboard).

The signature is generated by hashing the full JSON payload with a shared secret key (the signing key shown when the webhook was created), and it is sent in the X-Signature HTTP header of each request. This allows recipients to confirm that the payload has not been tampered with in transit and that it originated from Felloh.

To verify the signature on your end, compute the HMAC-SHA256 hash of the raw request body using your shared secret key. Then compare the result to the value in the X-Signature header. A matching signature confirms that the payload is valid

Webhook Verification

const crypto = require('crypto');

function verifySignature(rawBody, signature, secret) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

Delivery and Retries

  • Events are delivered as HTTP POST requests with a JSON body and a Content-Type of application/json.
  • Transaction status events are sent whenever a transaction reaches a final status, including declined payments, so always check the status field.
  • Any response other than a 2xx status is treated as a failed delivery. Transaction status events are retried up to 5 times, roughly a minute apart. Refund status, booking and user events are sent once and are not retried.
  • Respond quickly: a delivery that has not completed within 30 seconds is abandoned.
  • Delivery logs are available in the dashboard for 30 days. See the debugging webhooks guide.

Testing Webhooks

You can send a test event to any webhook you have registered from the Felloh dashboard, under Developer Configuration. Open a webhook and use Send a Test Event to pick an event type and deliver it to your endpoint.

Two kinds of payload are available:

  • Sample data — a fabricated event with recognisable test values. Use this to build against a payload before you have taken a payment, and to check the cases that are easy to miss, such as a declined transaction.
  • Replay a real event — one of your own recent transactions or refunds, rebuilt into the payload exactly as it was first sent. Use this to reproduce something that went wrong.

Both are produced by the same code that sends your live events, so the shape you see when testing is the shape you will receive in production.

Test events are signed with your real signing key, so your signature verification runs unchanged. They additionally carry an X-Felloh-Test header set to true. Check this header and discard the event if it is present — otherwise a test will look like a genuine payment to your system. A live event never carries it.

Test deliveries appear in your webhook logs marked as a test, and are not counted towards your delivery success rate. Unlike live events they are not retried: the response your endpoint gives is reported back to you in the dashboard, along with the status code, headers, body and the time your endpoint took to respond.

Ignoring Test Events

app.post('/webhooks/felloh', (req, res) => {
  if (req.get('X-Felloh-Test') === 'true') {
    return res.sendStatus(200);
  }

  // handle the real event
  return res.sendStatus(200);
});

Transaction Status Event

  • Name
    amount
    Type
    integer
    Description

    The total value of the transaction in the lowest denomination of the currency (e.g., pence for GBX, cents for USX).

  • Name
    booking.id
    Type
    uuid
    Description

    The booking ID the transaction is linked to.

  • Name
    booking.booking_reference
    Type
    string
    Description

    Your unique reference for the customer's booking.

  • Name
    completed_at
    Type
    datetime
    Description

    The datetime at which the transaction was completed by the customer.

  • Name
    currency
    Type
    string
    Description

    The currency of the transaction. See currency documentation.

  • Name
    payment_link.id
    Type
    uuid
    Description

    The payment link ID that was used to create the transaction, if any.

  • Name
    ecommerce.id
    Type
    uuid
    Description

    The ecommerce session ID that was used to create the transaction, if any.

  • Name
    provider.name
    Type
    string
    Description

    The provider or merchant acquirer name.

  • Name
    provider.reference
    Type
    string
    Description

    The provider or merchant acquirer reference for the transaction.

  • Name
    status
    Type
    string
    Description

    The status of the transaction.

  • Name
    transaction.id
    Type
    uuid
    Description

    The transaction ID.

  • Name
    transaction.narrative
    Type
    object
    Description

    An object with reason and description explaining the outcome of the transaction, for example why a payment was declined.

  • Name
    surcharge.surcharge_applied
    Type
    bool
    Description

    Whether a surcharge has been applied to the transaction

  • Name
    surcharge.amount
    Type
    integer
    Description

    The total value of the surcharge on the transaction in the lowest denomination of the currency (e.g., pence for GBX, cents for USX), or null when no surcharge was applied.

  • Name
    surcharge.currency
    Type
    string
    Description

    The currency of the surcharge, or null when no surcharge was applied.

Example Event Payload

{

  "amount": 10000,
  "booking": {
    "id": "3d38e53b-867a-43bd-ad5d-4af786dd1b33",
    "booking_reference": "test-123"
  },
  "completed_at": "2021-11-03T11:04:27.000Z",
  "currency": "GBX",
  "payment_link": { "id": "2cc9b939-d2fa-41e5-abae-39c377236978" },
  "ecommerce": { "id": null },
  "provider": {
    "name": "nuvei",
    "reference": "93216A82FBDD1696203B7FC6099A9B66.prod02-vm-tx94"
  },
  "status": "COMPLETE",
  "transaction": { "id": "2cc9b939-d3fa-41e5-acae-39c377236978" },
  "surcharge": {
    "surcharge_applied": true,
    "amount": 100,
    "currency": "GBX"
  }
}

Refund Status Event

  • Name
    authorisation_code
    Type
    string
    Description

    A unique code used to identify a refund.

  • Name
    actioned_at
    Type
    datetime
    Description

    The datetime at which the refund status change was actioned.

  • Name
    amount
    Type
    integer
    Description

    The amount that the refund was for

  • Name
    metadata
    Type
    object
    Description

    User defined metadata attached to the refund

  • Name
    status.id
    Type
    string
    Description

    The status of the refund. All statuses can be retrieved using the enums endpoint.

  • Name
    transaction.id
    Type
    uuid
    Description

    The transaction ID.

  • Name
    transaction.amount
    Type
    integer
    Description

    The amount that the transaction was for (customer was charged)

  • Name
    transaction.currency
    Type
    string
    Description

    The currency of the transaction | see currency documentation.

  • Name
    transaction.booking.booking_reference
    Type
    string
    Description

    The booking reference assigned to the booking (attached to the transaction)

  • Name
    transaction.booking.customer_name
    Type
    string
    Description

    The customer name assigned to the booking (attached to the transaction)

Example Event Payload

{

  "authorisation_code": "3d38e53b-867a-43bd-ad5d-4af786dd1b33",
  "actioned_at": "2021-11-03T11:04:27.000Z",
  "amount": 100,
  "metadata": { "internal_id": "abc-1234" },
  "status": {
    "id": "PENDING_AUTHORISATION"
  },
  "transaction": { 
    "id": "2cc9b939-d3fa-41e5-acae-39c377236978",
    "amount": 100,
    "currency": "GBX",
    "booking": {
      "booking_reference": "FE123456",
      "customer_name": "Caroline Rennie"
    }
  }
}

Booking Events

Two event types share this payload: Booking Creation (booking:create), sent when a booking is created, and Booking Deletion (booking:delete), sent when one is deleted. Subscribe to them independently.

Booking creation fires when a booking is created through the API — whether directly, or implicitly by creating a payment link or payment link group with a booking_reference. Bookings brought in by a bulk import are not announced, so an import does not flood your endpoint.

A booking created alongside a payment link carries no travel dates and no value, so treat departure_date, return_date and amount as optional.

  • Name
    booking.id
    Type
    uuid
    Description

    The booking ID.

  • Name
    booking.booking_reference
    Type
    string
    Description

    Your unique reference for the customer's booking.

  • Name
    customer_name
    Type
    string
    Description

    The name of the customer the booking is for.

  • Name
    email
    Type
    string
    Description

    The email address held against the booking.

  • Name
    amount
    Type
    integer
    Description

    The gross value of the booking in the lowest denomination of the currency (e.g., pence for GBX), or null when the booking was created without a value.

  • Name
    currency
    Type
    string
    Description

    The currency of the booking | see currency documentation.

  • Name
    departure_date
    Type
    date
    Description

    The departure date, or null when the booking has no travel dates.

  • Name
    return_date
    Type
    date
    Description

    The return date, or null when the booking has no travel dates.

  • Name
    customer.id
    Type
    uuid
    Description

    The customer record the booking is attached to, or null.

  • Name
    created_at
    Type
    datetime
    Description

    The datetime at which the booking was created.

  • Name
    deleted_at
    Type
    datetime
    Description

    The datetime at which the booking was deleted. Null on a creation event, populated on a deletion event.

Example Event Payload

{

  "booking": {
    "id": "3d38e53b-867a-43bd-ad5d-4af786dd1b33",
    "booking_reference": "FE123456"
  },
  "customer_name": "Caroline Rennie",
  "email": "caroline@example.com",
  "amount": 250000,
  "currency": "GBX",
  "departure_date": "2027-05-01",
  "return_date": "2027-05-14",
  "customer": { "id": "5c2d8e1f-4a6b-4c8d-9e0f-1a2b3c4d5e6f" },
  "created_at": "2026-10-16T11:04:27.000Z",
  "deleted_at": null
}

User Create Event

Sent when a user is added to your organisation, including API users. A person can belong to several organisations, so the organisation block tells you which one they were added to.

  • Name
    user.id
    Type
    string
    Description

    The user ID.

  • Name
    email
    Type
    string
    Description

    The user's email address.

  • Name
    first_name
    Type
    string
    Description

    The user's first name.

  • Name
    last_name
    Type
    string
    Description

    The user's last name.

  • Name
    status
    Type
    string
    Description

    The status of the user. All statuses can be retrieved using the enums endpoint.

  • Name
    is_api_user
    Type
    bool
    Description

    Whether the user is an API user rather than a person.

  • Name
    organisation.id
    Type
    string
    Description

    The organisation the user was added to.

  • Name
    organisation.name
    Type
    string
    Description

    The name of that organisation.

  • Name
    created_at
    Type
    datetime
    Description

    The datetime at which the user was created.

Example Event Payload

{

  "user": { "id": "4f2b8c91-6d3e-4a75-9b0c-1e2f3a4b5c6d" },
  "email": "caroline@example.com",
  "first_name": "Caroline",
  "last_name": "Rennie",
  "status": "ACTIVE",
  "is_api_user": false,
  "organisation": {
    "id": "X1234",
    "name": "Example Travel"
  },
  "created_at": "2026-10-16T11:04:27.000Z"
}