Webhooks (copy)
Integrate Bookingmood with a payment provider of your choice.
Why custom payment integrations
Bookingmood supports a few major payment providers like Stripe, Mollie and Square, out of the box.
As we serve users world-wide, each country may have its own payment provider.
Instead of building custom integrations with all of these, we expose primitives that allow you to build custom payment pipelines.
Custom payment setup
Custom payments can be configured from organization settings. There, set payment method to "Custom".
A custom payment configuration consists of 2 fields:
endpoint: The URL that will be called for generating payment linkssigning_secret: The secret we use to sign our requests, so that you can validate the request actually originates from Bookingmood.
Handling payment requests
When a guest initiates a payment (by clicking on the "pay" button), Bookingmood sends a POST request to endpoint.
This request contains a JSON body:
{
action: "PAYMENT_LINK_CREATE",
date: number, // Number of seconds since epoch
language: string, // User language identifier
payment_id, // ID of the payment the request is for
}You can request further data regarding the payment and the related booking using our API.
The endpoint should quickly return a successful status code (2xx) and a JSON object:
{
payment_url: string
}Example endpoint
// This example uses Express to receive requests
const express = require("express");
const app = express();
// Match the raw body to content type application/json
// If you are using Express v4 - v4.16 you need to use body-parser, not express, to retrieve the request body
app.post(
"/payment-link-generation-endpoint",
express.json({ type: "application/json" }),
(request, response) => {
const event = request.body;
// Handle the event
switch (event.action) {
case "PAYMENT_LINK_CREATE":
const payment = await bookingmoodApi
.from("payments")
.select("*")
.eq("id", event.payment_id)
.single();
return response.json({
payment_url: `https://custom-payment-provider.com/pay/${payment.amount - payment.paid}`
});
default:
throw new Error(`Unexpected action: ${event.action}`);
}
}
);
app.listen(8000, () => console.log("Running on port 8000"));Securing your endpoint
After confirming that your endpoint works as expected, secure the connection by implementing best practices.
Verify events are sent from Bookingmood
Bookingmood signs requests by including a signature in each request's X-Signature header. This allows you to verify that the events were sent by Bookingmood, not by a third party.
The following section describes how to verify webhook signatures:
Retrieve your endpoint’s secret from your organization's settings page.
Verify the signature.
To verify the signature that Bookingmood sends with each event, perform the following steps:
Prepare the signed payload by concatenating your
signing_secretwith the character.and then the stringified request body.Compute the expected signature by hashing the signed payload with the MD5 hash function.
Compare the expected signature to the signature in the
X-Signatureheader. If the signatures match, the event is verified.
Example of verifying signatures manually
// Set your secret key.
const signingSecret = "...";
// Make sure that the secret is never exposed to the client
// This example uses Express to receive webhooks
const express = require("express");
const crypto = require("crypto");
const app = express();
app.post(
"/payment-link-generation-endpoint",
express.json({ type: "application/json" }),
(request, response) => {
const signature = request.headers["x-signature"];
const signedPayload = `${signingSecret}.${JSON.stringify(request.body)}`;
const expectedSignature = crypto
.createHash("md5")
.update(signedPayload)
.digest("hex");
if (signature !== expectedSignature) {
response.status(401).send("Invalid signature");
return;
}
// Handle the event
}
);
app.listen(8000, () => console.log("Running on port 8000"));Preventing replay attacks
A replay attack is when an attacker intercepts a valid payload and its signature, then re-transmits them. To mitigate such attacks, Bookingmood includes a timestamp in the X-Signature header. Because this timestamp is part of the signed payload, it’s also verified by the signature, so an attacker can’t change the timestamp without invalidating the signature.
If the signature is valid but the timestamp is too old, you can have your application reject the payload.
We suggest to tolerate at most 5 minutes between the timestamp and the current time.
Bookingmood generates the timestamp and signature each time we send an event to your endpoint.
If Bookingmood retries an event (for example, your endpoint previously replied with a non-2xx status code), then we generate a new signature and timestamp for the new delivery attempt.