Skip to main content

Verifying Signatures

To verify that the webhooks that you receive at your endpoint came from Jiter, we provide a X-Jiter-Signature header as well as a X-Jiter-Request-Timestamp.

note

The Jiter-Signature header is deprecated, please use the X-Jiter-Signature header going forward.

Take a look at this example event below:

{
"headers": {
"Jiter-Signature": "l8gsSg2UgivhPJ302ywQ6Vc7NHn3HWhwzW0iswSEp8c=",
"X-Jiter-Signature": "R9MLg3fv09c2zFnZTb52lxiChzMMOqtsJglZjVU4NVs=",
"X-Jiter-Request-Timestamp": 1667402838551,
"Content-Type": "application/json"
},
"body": {
"payload": "{\"action\":\"buyGroceries\",\"values\":[\"eggs\",\"bacon\",\"pasta\",\"bread\"]}",
"scheduledTime": "2022-11-02T15:27:00.000Z"
}
}

To verify this was sent by Jiter, you can use the Jiter SDK in Express. You can also view our example project here

// In index.js with your routes

import { jiterWebhookEvent } from "./webhooks";

api.use("/webhooks/jiter", jiterWebhookEvent);
// In webhooks.js
const Jiter = require("@jiter/node").default;

module.exports = {
jiterWebhookEvent: Jiter.Middleware.webhookHandler(
({ payload }) => {
console.log("Signed, valid Jiter event received");
// Now that the event was verified and a response was sent, you can continue with the payload:
if (payload.action === "buyGroceries") {
console.log("Purchased the following groceries:", payload.items);
// await db.purchases.insertOne({
// items: payload.items
// }))
} else if (payload.action === "returnGroceries") {
const returns = payload.returns.map(
(item) => `${item.itemName} - ${item.reason}`
);
console.log("Returned the following groceries:", returns);
// await db.returns.insertOne({
// items: payload.returns
// }))
}
},
{ parse: true }
),
};

or verify the event manually

const signingSecret = process.env.SIGNING_SECRET;
const millisecondsUntilWebhookExpiration = 1000 * 60 * 2;

const body = typeof rawBody === "string" ? rawBody : JSON.stringify(rawBody);

const timeSinceRequest = Math.abs(Date.now() - Number(requestTimestamp));
if (Number.isNaN(timeSinceRequest)) {
// Invalid request timestamp
res.sendStatus(401);
return;
}

if (timeSinceRequest > millisecondsUntilWebhookExpiration) {
// The request timestamp expired
res.sendStatus(401);
return;
}

if (!body.trim()) {
// Invalid request body
res.sendStatus(401);
return;
}

if (!requestSignature.trim()) {
// Invalid request signature
res.sendStatus(401);
return;
}

const baseString = `${requestTimestamp}:${body}`;
const localSignature = createHmac("sha256", signingSecret)
.update(baseString)
.digest("base64");

if (requestSignature === localSignature) {
res.sendStatus(200);
}

const parsedBody = JSON.parse(body);
// Continue processing your event here
// await db.insertOne(body)