This guide is for implementing webhook endpoints in the Glance Demo Platform Val that follow the established architectural patterns.
- Before implementing, identify which pattern to use:
- Data API Pattern (/api/, /views/): Controllers return data objects, routes handle HTTP
- Webhook Handler Pattern (/tasks/*): Controllers handle HTTP requests/responses directly
For /tasks/* routes, use the Webhook Handler Pattern.
- Study these files to understand the established patterns:
/backend/controllers/glimpse.controller.ts
- Example of webhook handler pattern/backend/routes/tasks/_tasks.routes.ts
- Existing webhook route structure/backend/services/notion.service.ts
- Service layer patterns/backend/routes/webhookAuthCheck.ts
- Authentication middleware
- For a new /tasks/url endpoint, you'll need:
- Controller function (handles HTTP directly)
- Service function (if new Notion API calls needed)
- Route definition
- Documentation updates
If new external API calls are required:
- Location: /backend/services/notion.service.ts
- Pattern: Add pure API functions that return {success, data/error, timestamp} format
- Example: updatePageUrl(pageId, url) function
- Responsibilities: Only handle external API calls, no business logic
- Location:
/backend/controllers/tasks.controller.ts
- Pattern: Webhook Handler Pattern - function takes Context, returns HTTP responses
- Function signature: export async function handleNotionWebhook(c: Context)
- Responsibilities:
- Parse HTTP request (c.req.json(), c.req.header())
- Extract and validate data from webhook payload
- Implement business logic
- Call service functions
- Log success/failure with detailed context
- Return HTTP responses with appropriate status codes (c.json(data, statusCode))
- Location:
/backend/routes/tasks/_tasks.routes.ts
- Pattern: Direct controller call
- Implementation:
app.post("/url", handleNotionWebhook)
- Update catch-all handler: Add new endpoint to
availableEndpoints
array
Main README (/README.md)
:
- Add endpoint to "Webhooks (Notion Integration)" section
- Follow format:
POST /tasks/url
- Description (requiresX-API-KEY
header)
Route README (/backend/routes/tasks/README.md)
:
- Add comprehensive endpoint documentation
- Include request/response examples
- Document error cases
- Add testing instructions
Controller README (/backend/controllers/README.md)
:
- Add controller function to examples
- Note that it follows webhook handler pattern
Service README (/backend/services/README.md)
:
- Add any new service functions to examples
- All
/tasks/*
routes automatically use webhook authentication - Requires X-API-KEY header with NOTION_WEBHOOK_SECRET value
- No additional auth setup needed in route
Support multiple payload structures:
// Try multiple common webhook payload structures
if (body.data?.id) {
pageId = body.data.id;
} else if (body.id) {
pageId = body.id;
} else if (body.page_id) {
pageId = body.page_id;
}
Use this pattern for glimpse URLs:
const host = c.req.header("host");
const glimpseUrl = `https://${host}/glimpse/${pageId}`;
Return structured errors with appropriate HTTP status codes:
// 400 for client errors
return c.json({ success: false, error: "Description" }, 400);
// 500 for server errors
return c.json({ success: false, error: "Description", details: result.error }, 500);
Include comprehensive logging:
console.log("Processing URL update webhook request");
console.log("Notion webhook received:", body);
console.log(`Constructed glimpse URL: ${glimpseUrl} for page ID: ${pageId}`);
console.log("Successfully updated Notion page with glimpse URL:", { pageId, url });
console.error("Failed to update Notion page:", result.error);
Return detailed success responses:
return c.json({
success: true,
message: "Page URL updated successfully",
pageId: pageId,
url: glimpseUrl,
timestamp: new Date().toISOString()
});
- Test without header (expect 401)
- Test with wrong key (expect 403)
- Test with correct key (expect 200 or business logic response)
- Test with valid payload structure
- Test with missing page ID (expect 400)
- Test with missing host header (expect 400)
- Test with Notion API failures (expect 500)
Create a test endpoint that simulates the logic without calling external APIs:
app.post("/test-url", async (c) => {
// Same logic but return test response instead of calling Notion API
});
- Don't mix patterns: /tasks/_ routes should use webhook handler pattern, not data API pattern
- Don't forget authentication: All /tasks/_ routes are automatically protected
- Don't skip logging: Webhook endpoints need comprehensive logging for debugging
- Don't forget documentation: Update all relevant README files
- Don't hardcode hosts: Always extract host from request headers
- Don't forget error cases: Handle missing data, API failures, etc.
- Don't skip the catch-all update: Add new endpoints to available endpoints list
Before considering implementation complete:
- Controller follows webhook handler pattern (takes Context, returns HTTP responses)
- Route uses direct controller call pattern
- Service functions return consistent {success, data/error, timestamp} format
- Comprehensive logging for success and failure cases
- Proper HTTP status codes (400 for client errors, 500 for server errors)
- Support for multiple webhook payload structures
- Host extraction from request headers
- Updated all relevant documentation
- Added endpoint to catch-all handler's available endpoints list
- Tested authentication (should fail with test keys)
- Error handling for all edge cases
- The webhook authentication middleware handles all security automatically
- Focus on business logic and proper HTTP handling in the controller
- Keep service functions pure (no business logic, just API calls)
- Document the architectural decision when mixing patterns
- Test thoroughly but remember authentication will block test requests without proper keys
This pattern ensures consistency with the established architecture while providing clear separation of concerns and comprehensive error handling.