Before you can release an app, it must verify that any HTTP requests it receives originate are actually being sent from Canva (and not from some nefarious third-party). This protects your app — and our users — from a variety of attacks.
All HTTP requests that Canva sends to an app need to be verified. This includes POST
requests, such as the request sent to the /content/resources/find
endpoint, and GET
requests, such as the request sent to the app's Redirect URL.
If an app supports authentication, it must also verify the authenticity of the authentication flow. To learn more, refer to Authentication.
Before submitting an app for review, must run a signature verification test via the Developer Portal. This test sends requests to the app's endpoints and confirms if the requests are verified. You can't submit the app until the test has passed.
To learn more, refer to Test signature verification.
The steps for verifying a request depend on whether it's a GET
request or a POST
request. To learn more, refer to the following guides:
This example demonstrates how to verify POST
and GET
requests in Express.js.
const { createHmac } = require('crypto');const express = require('express');const app = express();app.use(express.json({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);return;}response.send({type: 'SUCCESS',resources: [],});});app.get('/my-redirect-url', async (request, response) => {if (!isValidGetRequest(process.env.CLIENT_SECRET, request)) {response.sendStatus(401);return;}response.sendStatus(200);});const isValidPostRequest = (secret, request) => {// Verify the timestampconst sentAtSeconds = request.header('X-Canva-Timestamp');const receivedAtSeconds = new Date().getTime() / 1000;if (!isValidTimestamp(sentAtSeconds, receivedAtSeconds)) {return false;}// Construct the messageconst version = 'v1';const timestamp = request.header('X-Canva-Timestamp');const path = getPathForSignatureVerification(request.path);const body = request.rawBody;const message = `${version}:${timestamp}:${path}:${body}`;// Calculate a signatureconst signature = calculateSignature(secret, message);// Reject requests with invalid signaturesif (!request.header('X-Canva-Signatures').includes(signature)) {return false;}return true;};const isValidGetRequest = (secret, request) => {// Verify the timestampconst sentAtSeconds = request.query.time;const receivedAtSeconds = new Date().getTime() / 1000;if (!isValidTimestamp(sentAtSeconds, receivedAtSeconds)) {return false;}// Construct the messageconst version = 'v1';const { time, user, brand, extensions, state } = request.query;const message = `${version}:${time}:${user}:${brand}:${extensions}:${state}`;// Calculate a signatureconst signature = calculateSignature(secret, message);// Reject requests with invalid signaturesif (!request.query.signatures.includes(signature)) {return false;}return true;};const isValidTimestamp = (sentAtSeconds,receivedAtSeconds,leniencyInSeconds = 300,) => {return (Math.abs(Number(sentAtSeconds) - Number(receivedAtSeconds)) <Number(leniencyInSeconds));};const getPathForSignatureVerification = (input) => {const paths = ['/configuration','/configuration/delete','/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 secretconst key = Buffer.from(secret, 'base64');// Calculate the signaturereturn createHmac('sha256', key).update(message).digest('hex');};app.listen(process.env.PORT || 3000);