Agent skill
canva-webhooks-events
Implement Canva Connect API webhook handling with JWK signature verification. Use when setting up webhook endpoints, handling Canva event notifications, or implementing real-time design collaboration features. Trigger with phrases like "canva webhook", "canva events", "canva notifications", "handle canva events", "canva JWK".
Install this agent skill to your Project
npx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/main/plugins/saas-packs/canva-pack/skills/canva-webhooks-events
SKILL.md
Canva Webhooks & Events
Overview
Receive real-time notifications from Canva via webhooks when users comment on designs, request folder access, share designs, or interact with your integration. Canva sends signed JWT payloads verified with public JWK keys.
Prerequisites
- Canva integration with
collaboration:eventscope enabled - HTTPS endpoint accessible from the internet
- JWK verification library (
joserecommended) - Webhook URL configured in your integration settings
Setup
Step 1: Configure Webhooks in Canva
- Go to your integration settings at canva.dev
- Under Notifications, enable collaboration:event
- Enter your webhook URL:
https://your-app.com/webhooks/canva - Save the configuration
Step 2: Implement JWK Signature Verification
// src/canva/webhooks.ts
import { createRemoteJWKSet, jwtVerify, JWTPayload } from 'jose';
// Canva publishes public keys at this endpoint
// GET https://api.canva.com/rest/v1/connect/keys
const CANVA_JWKS = createRemoteJWKSet(
new URL('https://api.canva.com/rest/v1/connect/keys')
);
interface CanvaWebhookPayload extends JWTPayload {
notification_type: string;
notification: Record<string, any>;
timestamp: string;
user_id: string;
team_id: string;
}
export async function verifyCanvaWebhook(
rawBody: string
): Promise<CanvaWebhookPayload | null> {
try {
const { payload } = await jwtVerify(rawBody, CANVA_JWKS, {
issuer: 'canva',
});
return payload as CanvaWebhookPayload;
} catch (error) {
console.error('Webhook verification failed:', error);
return null;
}
}
Step 3: Express Webhook Endpoint
import express from 'express';
import { verifyCanvaWebhook } from './canva/webhooks';
const app = express();
// IMPORTANT: Accept raw text body for JWT verification
app.post('/webhooks/canva',
express.text({ type: '*/*' }),
async (req, res) => {
const payload = await verifyCanvaWebhook(req.body);
if (!payload) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Must return 200 to acknowledge — other codes = error
res.status(200).json({ received: true });
// Process async to avoid timeout
handleCanvaEvent(payload).catch(console.error);
}
);
Step 4: Event Handler
// Canva webhook notification types
type CanvaNotificationType =
| 'comment' // New comment on a design
| 'design_access_requested' // Someone requests design access
| 'design_approval_requested' // Approval workflow triggered
| 'design_approval_response' // Approval accepted/rejected
| 'design_mention' // User @mentioned in a design
| 'folder_access_requested' // Folder access request
| 'design_shared' // Design shared with user
| 'folder_shared' // Folder shared with user
| 'suggestion' // Design suggestion made
| 'team_invite' // Team invitation
| 'design_approval_reviewer_invalidated'; // Reviewer removed
const handlers: Record<string, (notification: any) => Promise<void>> = {
comment: async (data) => {
console.log(`Comment on design ${data.design_id}: ${data.message}`);
// Sync comment to your ticketing system, Slack, etc.
},
design_access_requested: async (data) => {
console.log(`Access requested for design ${data.design_id} by user ${data.requesting_user_id}`);
// Auto-approve or notify admin
},
design_shared: async (data) => {
console.log(`Design ${data.design_id} shared with permissions: ${data.permissions}`);
// Update your access control records
},
folder_access_requested: async (data) => {
console.log(`Folder access requested: ${data.folder_id}`);
},
};
async function handleCanvaEvent(payload: CanvaWebhookPayload): Promise<void> {
const handler = handlers[payload.notification_type];
if (!handler) {
console.log(`Unhandled notification type: ${payload.notification_type}`);
return;
}
try {
await handler(payload.notification);
console.log(`Processed ${payload.notification_type} at ${payload.timestamp}`);
} catch (error) {
console.error(`Failed to process ${payload.notification_type}:`, error);
}
}
Required Scopes per Event Type
| Event | Required Scopes |
|---|---|
| comment | collaboration:event, design:meta:read, comment:read |
| design_access_requested | collaboration:event, design:meta:read |
| design_shared | collaboration:event, design:meta:read |
| folder_access_requested | collaboration:event, folder:read |
| design_mention | collaboration:event, design:meta:read |
Scopes are explicit — you must enable each one individually.
Idempotency
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
async function processIdempotent(
notificationId: string,
handler: () => Promise<void>
): Promise<void> {
const key = `canva:webhook:${notificationId}`;
const alreadyProcessed = await redis.set(key, '1', 'EX', 604800, 'NX'); // 7 days, NX = only if not exists
if (!alreadyProcessed) {
console.log(`Duplicate webhook skipped: ${notificationId}`);
return;
}
await handler();
}
Testing Webhooks Locally
# 1. Expose local server via ngrok
ngrok http 3000
# 2. Set the ngrok HTTPS URL in your Canva integration settings
# e.g., https://abc123.ngrok-free.app/webhooks/canva
# 3. Trigger events by commenting on a design shared with the authorized user
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| 401 on webhook | JWK verification failed | Check jose library version, key URL |
| No events received | Webhook URL not configured | Set URL in integration settings |
| Duplicate events | No idempotency | Implement notification ID tracking |
| Events delayed | Processing too slow | Return 200 immediately, process async |
| Missing event types | Scopes not enabled | Enable collaboration:event + per-type scopes |
Resources
Next Steps
For performance optimization, see canva-performance-tuning.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
dockerfile-generator
Dockerfile Generator - Auto-activating skill for DevOps Basics. Triggers on: dockerfile generator, dockerfile generator Part of the DevOps Basics skill category.
branch-naming-helper
Branch Naming Helper - Auto-activating skill for DevOps Basics. Triggers on: branch naming helper, branch naming helper Part of the DevOps Basics skill category.
readme-generator
Readme Generator - Auto-activating skill for DevOps Basics. Triggers on: readme generator, readme generator Part of the DevOps Basics skill category.
makefile-generator
Makefile Generator - Auto-activating skill for DevOps Basics. Triggers on: makefile generator, makefile generator Part of the DevOps Basics skill category.
gitignore-generator
Gitignore Generator - Auto-activating skill for DevOps Basics. Triggers on: gitignore generator, gitignore generator Part of the DevOps Basics skill category.
pre-commit-hook-setup
Pre Commit Hook Setup - Auto-activating skill for DevOps Basics. Triggers on: pre commit hook setup, pre commit hook setup Part of the DevOps Basics skill category.
Didn't find tool you were looking for?