Introduction

Verifying a Webhook Request

To ensure that you only process legitimate requests from Ramp, verify the signature included with every webhook request.

All valid requests include an X-Ramp-Signature header containing an HMAC-SHA256 signature. Ramp generates this signature from the exact JSON request body using your latest active secret key.

The following Express example verifies the signature before handling the event:

const crypto = require("crypto");
const express = require("express");

const app = express();
const secretKey = process.env.RAMP_SECRET_KEY;

app.post(
  "/your_webhook_url",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const signature = crypto
      .createHmac("sha256", secretKey)
      .update(req.body)
      .digest("hex");

    if (signature !== req.get("X-Ramp-Signature")) {
      return res.status(401).send("Invalid signature");
    }

    const payload = JSON.parse(req.body.toString("utf8"));
    await handleWebhook(payload);

    res.sendStatus(200);
  }
);

Use the raw request body when calculating the signature. Parsing and re-serializing the JSON before verification may change the payload and cause a valid signature to be rejected. Keep your secret key in secure server-side
configuration and never expose it in client-side code.

Responding to a Webhook Request

Return an HTTP 2xx status code as soon as the webhook has been accepted. Ramp only uses the response status code to determine whether delivery succeeded; the response body is not processed.

Perform lengthy work in a background job so that your endpoint responds within the 10-second timeout.

Retries

If Ramp receives a non-2xx response, cannot connect to your endpoint, or the request times out, it retries the webhook automatically.

By default, Ramp makes five attempts in total: the initial delivery and up to four retries. Retries are scheduled after approximately 1, 2, 4, and 8 minutes. A small amount of random jitter is added to each delay.

The same webhook may therefore arrive more than once. Make your handler idempotent by storing a unique key such as event plus data.public_id, and return 200 for an event that has already been accepted.


Did this page help you?