const { createHmac } = require("crypto");
const express = require("express");
verify: (request, response, buffer) => {
request.rawBody = buffer.toString();
app.post("/content/resources/find", async (request, response) => {
if (!isValidPostRequest(process.env.CLIENT_SECRET, request)) {
response.sendStatus(401);
app.get("/my-redirect-url", async (request, response) => {
if (!isValidGetRequest(process.env.CLIENT_SECRET, request)) {
response.sendStatus(401);
response.sendStatus(200);
const isValidPostRequest = (secret, request) => {
const sentAtSeconds = request.header("X-Canva-Timestamp");
const receivedAtSeconds = new Date().getTime() / 1000;
if (!isValidTimestamp(sentAtSeconds, receivedAtSeconds)) {
const timestamp = request.header("X-Canva-Timestamp");
const path = getPathForSignatureVerification(request.path);
const body = request.rawBody;
const message = `${version}:${timestamp}:${path}:${body}`;
const signature = calculateSignature(secret, message);
// Reject requests with invalid signatures
if (!request.header("X-Canva-Signatures").includes(signature)) {
const isValidGetRequest = (secret, request) => {
const sentAtSeconds = request.query.time;
const receivedAtSeconds = new Date().getTime() / 1000;
if (!isValidTimestamp(sentAtSeconds, receivedAtSeconds)) {
const { time, user, brand, extensions, state } = request.query;
const message = `${version}:${time}:${user}:${brand}:${extensions}:${state}`;
const signature = calculateSignature(secret, message);
// Reject requests with invalid signatures
if (!request.query.signatures.includes(signature)) {
const isValidTimestamp = (
Math.abs(Number(sentAtSeconds) - Number(receivedAtSeconds)) <
Number(leniencyInSeconds)
const getPathForSignatureVerification = (input) => {
"/content/resources/find",
"/editing/image/process",
"/editing/image/process/get",
"/publish/resources/find",
"/publish/resources/get",
"/publish/resources/upload",
return paths.find((path) => input.endsWith(path));
const calculateSignature = (secret, message) => {
// Decode the client secret
const key = Buffer.from(secret, "base64");
// Calculate the signature
return createHmac("sha256", key).update(message).digest("hex");
app.listen(process.env.PORT || 3000);