Because of the asynchronous nature of the direct debit rails, a lot of events happen after Palomma has sent back an HTTP response to any of the above endpoints. Therefore, a Payment Request might initially come back as "pending" and change to "approved" some time after. Although a polling strategy where a merchant continuously polls the GET /paymentRequests/{paymentRequestId} for all payment request objects that are pending would work, Palomma also sends important updates (known as webhooks) as POST requests to a URL of the merchant’s choice.

Technical Overview

The endpoint the merchant selects must be configured to receive HTTPS POST requests with a JSON body (HTTP is acceptable for the sandbox environment), and must reply with a 200 status within 5 seconds to be marked as successfully delivered by Palomma. In case of an unsuccessful delivery (where Palomma doesn’t receive a 200 response back), Palomma might try to redeliver the request (though retries aren’t guaranteed).

Authentication

Every webhook Palomma sends is signed to verify the integrity of the data being sent in the webhook. This signature is generated by computing an HMAC-SHA-256 of the encoded body of the POST request with an integrityKey that is assigned to a merchant when they enable webhooks. We strongly recommend verifying the signature to make sure it was sent by Palomma before trusting the content of the webhook.

The recommended flow is the following:

  1. Retrieve the request headers X-Encoded-Data and X-Signature.
  2. X-Signature is an HMAC with the SHA256 hash function of X-Encoded-Data. Compute an HMAC-SHA-256 of X-Encoded-Data with the integrityKey assigned to you, and compare it to X-Signature. If the computed signature and X-Signature are not equal, the signature is invalid.
  3. X-Encoded-Data is the base64 encoding of the webhook’s payload (the body of the POST request). Decode it, parse it into a JSON object, and verify that the resulting JSON object is equal to the body of the webhook request. If this check fails, the signature is invalid.

If the above checks passed, you can be confident that the data was sent by Palomma and that it wasn’t tampered with, as long as the integrityKey hasn’t been compromised, since only Palomma and the merchant have access to the integrityKey and it’s implausible to generate the HMAC-SHA-256 of a message, secret key pair without the secret key. You should only proceed to processing the request if the signature is deemed valid. Otherwise, return an error and ignore the request.

Example

Here’s an example of the steps described above using Node.js.

webhookExample.js
const crypto = require("crypto");

app.post("/", (req, res) => {
  try {
    // Retrieve information from headers to perform validation.
    const encoded_data = req.headers["x-encoded-data"];
    const signature = req.headers["x-signature"];

    // The integrity key provided by Palomma.
    const integrityKey = process.env.PALOMMA_INTEGRITY_KEY;

    // Create an HMAC for validation purposes.
    const signatureRequest = crypto
      .createHmac("sha256", integrityKey)
      .update(encoded_data)
      .digest("hex");

    // Decode the base64-encoded string.
    const decodedData = Buffer.from(encoded_data, "base64").toString("utf-8");

    // Check if the signature matches and the decoded data is equal to the webhook request body.
    if (
      signature === signatureRequest &&
      decodedData === JSON.stringify(req.body)
    ) {
      // Code to execute when a valid webhook is received.
      res.status(200).json({ msg: "Success: Webhook is valid!" });
    } else {
      // Response sent when the webhook is invalid.
      res.status(401).json({ msg: "Error: Webhook is NOT valid!" });
    }
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

Duplicate Requests

In cases where a request is retried, the same webhookId and timestamp will be sent. We recommend that the handling of requests be idempotent, so that if you see a webhookId again after having successfully processed it, you ignore subsequent identical requests. You might want to cache requests processed webhookIds for a few days (eg. 2 days). As a request comes in, we recommend that you:

  1. Verify that the timestamp is no older than 2 days. If it is, ignore the request.
  2. Verify that you have not processed the webhookId in the past two days. If you have, ignore the request.

Request Structure

Headers

PropertyDescription
X-Encoded-DataBase64 encoding of the serialized webhook’s payload. Since the webhook is sent as an HTTP POST request, the payload is represented at the body of the request. In verifying that the request’s signature is valid, you want to first verify that X-Encoded-Data does in fact equal the webhook’s payload when decoded and parsed.
X-SignatureSignature of the webhook’s payload used to verify the data’s integrity. Generated by computing an HMAC with the SHA256 hash function of X-Encoded-Data with the integrityKey for the merchant. To verify that the request payload was written by someone with access to the integrityKey, you should compute an HMAC-SHA-256 of X-Encoded-Data with the integrityKey and verify that the computed HMAC equals X-Signature.

Body

PropertyTypeDescription
webhookIdstring (UUID)Unique ID for webhook notification. Should only be repeated if the notification fails to deliver, in which case more attempts to deliver the notification might be made.
timestampstringISO string for when the webhook notification was created.
eventTypestringEither ‘payment-request.update’ or ‘payment-method.update’. This specifies which event is being updated, in relation to the endpoint where it was created (POST /paymentMethod, POST /paymentRequest).
paymentMethodPayment Method Payment Method as it looks after the update that triggered the webhook notification, either one of payment method or payment request is present in the body.
paymentRequestPayment RequestPayment Request as it looks after the update that triggered the webhook notification, either one of payment method or payment request is present in the body.