Testing Webhooks
Test your webhook endpoint before deploying to production using local tunneling, test services, and the Editframe dashboard.
Local Testing with ngrok
Test webhooks locally by exposing your development server with ngrok:
1. Install ngrok
# macOS (Homebrew)brew install ngrok# Or download from https://ngrok.com/download
2. Start Your Local Server
# Start your applicationnpm run dev# Server running on http://localhost:3000
3. Create ngrok Tunnel
# Expose port 3000ngrok http 3000
ngrok will output:
Forwarding https://abc123.ngrok.io -> http://localhost:3000
4. Configure Webhook URL
Update your API key's webhook URL to the ngrok URL:
https://abc123.ngrok.io/webhooks/editframe
You can do this via:
- Editframe dashboard → API Keys → Edit
- Or programmatically via API
5. Trigger Test Webhooks
Trigger events to test your endpoint:
import { Client, createRender } from "@editframe/api";const client = new Client(process.env.EDITFRAME_API_KEY);// Create a render - triggers render.created webhookconst render = await createRender(client, {html: `<ef-timegroup mode="contain" class="w-[1920px] h-[1080px]"><ef-text>Test</ef-text></ef-timegroup>`,width: 1920,height: 1080,fps: 30,});// Wait for completion - triggers render.completed webhookconsole.log("Check your local server for webhook events");
6. Monitor Requests
View all webhook requests in the ngrok dashboard:
http://127.0.0.1:4040
The dashboard shows:
- Request headers (including
X-Webhook-Signature) - Request body (webhook event)
- Response status and body
- Timing information
Testing with webhook.site
Use webhook.site to inspect webhook payloads without writing code:
1. Get a Test URL
Visit webhook.site to get a unique URL:
https://webhook.site/abc-123-def
2. Configure Webhook
Set your API key's webhook URL to the webhook.site URL.
3. Trigger Events
Create renders or upload files to trigger webhooks.
4. Inspect Payloads
View webhook requests in webhook.site's interface:
- Full request headers
- JSON payload
- Signature verification (manual)
Note: webhook.site doesn't verify signatures. Use this for payload inspection only.
Test Webhook from Dashboard
Send test webhooks directly from the Editframe dashboard:
1. Navigate to API Key
Go to your API key detail page:
https://editframe.com/resource/api_keys/<key-id>
2. Send Test Webhook
- Click "Test Webhook"
- Select a topic (e.g., "render.completed")
- Click "Send Test"
The dashboard will send a test webhook with sample data:
{"topic": "webhook.test","data": {"id": "your-api-key-id","org_id": "your-org-id"}}
3. Verify Receipt
Check your webhook endpoint logs to verify:
- Request received
- Signature verified successfully
- Event processed
Unit Testing
Test webhook signature verification in unit tests:
import { describe, test, expect } from "vitest";import crypto from "node:crypto";import { verifyWebhookSignature } from "./webhooks";describe("webhook signature verification", () => {const secret = "test-webhook-secret";test("verifies valid signature", () => {const payload = JSON.stringify({topic: "render.completed",data: { id: "test-id" }});const signature = crypto.createHmac("sha256", secret).update(payload).digest("hex");expect(verifyWebhookSignature(payload, signature, secret)).toBe(true);});test("rejects invalid signature", () => {const payload = JSON.stringify({topic: "render.completed",data: { id: "test-id" }});const signature = "invalid-signature";expect(verifyWebhookSignature(payload, signature, secret)).toBe(false);});test("rejects tampered payload", () => {const payload = JSON.stringify({topic: "render.completed",data: { id: "test-id" }});const signature = crypto.createHmac("sha256", secret).update(payload).digest("hex");// Tamper with payloadconst tamperedPayload = payload.replace("test-id", "hacked-id");expect(verifyWebhookSignature(tamperedPayload, signature, secret)).toBe(false);});});
Integration Testing
Test webhook handling end-to-end:
import { describe, test, expect } from "vitest";import request from "supertest";import crypto from "node:crypto";import { app } from "./app"; // Your Express appdescribe("webhook endpoint", () => {const secret = process.env.EDITFRAME_WEBHOOK_SECRET!;function signPayload(payload: object): string {const body = JSON.stringify(payload);return crypto.createHmac("sha256", secret).update(body).digest("hex");}test("accepts valid webhook", async () => {const payload = {topic: "render.completed",data: {id: "test-render-id",status: "complete",download_url: "https://example.com/video.mp4"}};const signature = signPayload(payload);const response = await request(app).post("/webhooks/editframe").set("X-Webhook-Signature", signature).send(payload);expect(response.status).toBe(200);});test("rejects invalid signature", async () => {const payload = {topic: "render.completed",data: { id: "test-id" }};const response = await request(app).post("/webhooks/editframe").set("X-Webhook-Signature", "invalid").send(payload);expect(response.status).toBe(401);});test("rejects missing signature", async () => {const payload = {topic: "render.completed",data: { id: "test-id" }};const response = await request(app).post("/webhooks/editframe").send(payload);expect(response.status).toBe(401);});});
Manual Testing Script
Create a script to send test webhooks:
// scripts/send-test-webhook.tsimport crypto from "node:crypto";const WEBHOOK_URL = "http://localhost:3000/webhooks/editframe";const WEBHOOK_SECRET = process.env.EDITFRAME_WEBHOOK_SECRET!;async function sendTestWebhook(topic: string, data: object) {const payload = JSON.stringify({ topic, data });const signature = crypto.createHmac("sha256", WEBHOOK_SECRET).update(payload).digest("hex");console.log(`Sending ${topic} webhook...`);const response = await fetch(WEBHOOK_URL, {method: "POST",headers: {"Content-Type": "application/json","X-Webhook-Signature": signature,},body: payload,});console.log(`Response: ${response.status} ${response.statusText}`);console.log(await response.text());}// Test render.completedawait sendTestWebhook("render.completed", {id: "test-render-id",status: "complete",created_at: new Date().toISOString(),completed_at: new Date().toISOString(),failed_at: null,width: 1920,height: 1080,fps: 30,byte_size: 15728640,duration_ms: 5000,md5: "098f6bcd4621d373cade4e832627b4f6",metadata: null,download_url: "https://example.com/video.mp4"});
Run the script:
npx tsx scripts/send-test-webhook.ts
Monitoring Webhook Logs
Check webhook delivery logs in the Editframe dashboard:
- Go to your API key detail page
- View "Webhook Deliveries" section
- See:
- Delivery timestamp
- HTTP status code
- Response headers/body
- Retry attempts
- Success/failure status
Use these logs to debug webhook issues.
Testing Checklist
Before deploying to production:
- Signature verification works correctly
- Endpoint responds with 200 OK within 30 seconds
- Invalid signatures are rejected with 401
- Events are processed idempotently
- Errors don't crash the server
- Events are logged for debugging
- Retry logic handles transient failures
- ngrok tunnel tested successfully
- Test webhook from dashboard works
- Integration tests pass
Common Testing Issues
ngrok URL Not Accessible
Problem: Webhooks aren't reaching your local server
Solution:
- Check ngrok is running:
http://127.0.0.1:4040 - Verify ngrok URL in API key configuration
- Check local server is running on correct port
- Check firewall settings
Signature Verification Fails
Problem: All webhooks rejected with 401
Solution:
- Verify you're using the correct webhook secret
- Hash the raw request body, not parsed JSON
- Check secret isn't corrupted (no extra spaces/newlines)
- Test with the manual testing script
Webhooks Not Received
Problem: Events triggered but no webhooks received
Solution:
- Check webhook URL is correct
- Verify webhook events are selected in API key config
- Check webhook delivery logs for errors
- Test with webhook.site to isolate issues
Next Steps
- troubleshooting.md — Debug webhook issues
- security.md — Verify signatures correctly
- events.md — Understand webhook payloads