Skip to main content

Endpoint Setup

Learn how to set up your own error reporting endpoint to receive errors from your Foundry VTT modules.

Overview

Errors and Echoes forwards error reports to configured endpoints using a standard HTTP API. This allows module authors to use any backend service they prefer - from simple webhooks to sophisticated monitoring platforms.

Standard API Protocol

All endpoints must implement this standard protocol for compatibility.

Error Reporting Endpoint

URL Pattern: POST /report/{author}

Your endpoint will receive error reports at this URL pattern, where {author} is your author identifier.

Request Format

{
"error": {
"message": "Error description",
"stack": "Error stack trace",
"type": "Error",
"source": "module-name"
},
"attribution": {
"moduleId": "module-name",
"confidence": "high|medium|low",
"method": "automatic|manual",
"source": "stack-trace|user-report"
},
"foundry": {
"version": "12.331",
"system": {
"id": "dnd5e",
"version": "3.0.0"
},
"modules": [
{"id": "module-id", "version": "1.0.0"}
],
"scene": "Scene Name"
},
"meta": {
"timestamp": "2025-06-04T00:00:00.000Z",
"privacyLevel": "minimal|standard|detailed",
"reporterVersion": "0.1.2"
},
"client": {
"sessionId": "anonymous-session-id",
"browser": "Chrome 91.0"
},
"moduleContext": {
// Custom context from your module's context provider
}
}

Response Format

Your endpoint should respond with this JSON format:

{
"success": true,
"eventId": "fc6d8c0c43fc4630ad850ee518f1b9d0",
"message": "Error report received and processed successfully",
"timestamp": "2025-06-04T00:00:10.010Z",
"endpoint": "your-endpoint-name"
}

Error Response Format

For failures, return:

{
"success": false,
"message": "Detailed error description",
"timestamp": "2025-06-04T00:00:10.010Z",
"endpoint": "your-endpoint-name",
"retryAfter": 300
}

Implementation Examples

Express.js Endpoint

Simple Node.js server using Express:

const express = require('express');
const app = express();

app.use(express.json());

// Error reporting endpoint
app.post('/report/:author', (req, res) => {
const { author } = req.params;
const errorReport = req.body;

console.log(`Error from ${author}:`, errorReport.error.message);

// Process the error (save to database, send to monitoring service, etc.)
const eventId = generateEventId();

res.json({
success: true,
eventId: eventId,
message: 'Error report received',
timestamp: new Date().toISOString(),
endpoint: 'my-error-service'
});
});

// Connectivity test endpoint
app.post('/test/:author', (req, res) => {
const { author } = req.params;

res.json({
success: true,
eventId: 'test-' + Date.now(),
message: `Connectivity test successful for ${author}`,
timestamp: new Date().toISOString(),
endpoint: 'my-error-service'
});
});

// Health check
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
service: 'my-error-service'
});
});

function generateEventId() {
return crypto.randomUUID().replace(/-/g, '');
}

app.listen(3000, () => {
console.log('Error reporting server running on port 3000');
});

Cloudflare Workers

Serverless implementation using Cloudflare Workers:

export default {
async fetch(request, env) {
const url = new URL(request.url);

if (url.pathname.startsWith('/report/')) {
return handleErrorReport(request, env);
} else if (url.pathname.startsWith('/test/')) {
return handleTest(request, env);
} else if (url.pathname === '/health') {
return handleHealth();
}

return new Response('Not Found', { status: 404 });
}
};

async function handleErrorReport(request, env) {
if (request.method !== 'POST') {
return new Response('Method Not Allowed', { status: 405 });
}

const errorReport = await request.json();

// Forward to your monitoring service (Sentry, etc.)
const eventId = await forwardToMonitoring(errorReport, env);

return new Response(JSON.stringify({
success: true,
eventId: eventId,
message: 'Error forwarded to monitoring service',
timestamp: new Date().toISOString(),
endpoint: 'cloudflare-worker'
}), {
headers: { 'Content-Type': 'application/json' }
});
}

Discord Webhook

Forward errors to a Discord channel:

async function forwardToDiscord(errorReport, webhookUrl) {
const embed = {
title: `🚨 Error in ${errorReport.attribution.moduleId}`,
description: errorReport.error.message,
color: 0xff0000,
fields: [
{
name: 'Foundry Version',
value: errorReport.foundry.version,
inline: true
},
{
name: 'Module',
value: errorReport.attribution.moduleId,
inline: true
},
{
name: 'Confidence',
value: errorReport.attribution.confidence,
inline: true
}
],
timestamp: errorReport.meta.timestamp
};

const payload = {
embeds: [embed]
};

const response = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});

return response.ok;
}

Reference Implementation

A complete reference implementation is available at sentry-relay. This implementation:

  • Forwards to Sentry: Integrates with Sentry for production error monitoring
  • Security Features: Includes rate limiting, input validation, and CORS handling
  • Multi-Author Support: Routes errors to different Sentry projects by author
  • Cloudflare Workers: Serverless deployment with global edge locations

Using the Reference Implementation

  1. Fork the Repository: Start with the sentry-relay code
  2. Configure Sentry: Set up Sentry projects for your modules
  3. Deploy to Cloudflare: Use Wrangler to deploy the worker
  4. Set Environment Variables: Configure your Sentry DSN and allowed origins

Security Considerations

Input Validation

Always validate incoming data:

function validateErrorReport(report) {
if (!report.error?.message) {
throw new Error('Missing error message');
}
if (!report.attribution?.moduleId) {
throw new Error('Missing module ID');
}
if (!report.meta?.timestamp) {
throw new Error('Missing timestamp');
}

// Additional validation...
}

Rate Limiting

Implement rate limiting to prevent abuse:

const rateLimit = new Map();

function checkRateLimit(ip, maxRequests = 100, windowMs = 3600000) {
const now = Date.now();
const requests = rateLimit.get(ip) || [];

// Remove old requests
const recentRequests = requests.filter(time => now - time < windowMs);

if (recentRequests.length >= maxRequests) {
return false; // Rate limited
}

recentRequests.push(now);
rateLimit.set(ip, recentRequests);
return true;
}

CORS Configuration

Configure CORS for your Foundry domains:

function getCORSHeaders(request) {
const origin = request.headers.get('Origin');
const allowedOrigins = [
'https://your-foundry-domain.com',
'http://localhost:30000' // For local development
];

if (allowedOrigins.includes(origin)) {
return {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '86400'
};
}

return {};
}

Testing Your Endpoint

Connectivity Test

Errors and Echoes can test your endpoint:

// Test from Foundry console
const result = await ErrorReporter.testEndpoint('https://your-endpoint.com/test/your-name');
console.log('Test result:', result);

Manual Testing

Test with curl:

# Test connectivity
curl -X POST -H "Content-Type: application/json" \
-d '{"test": true, "timestamp": "2025-06-04T00:00:00.000Z"}' \
https://your-endpoint.com/test/your-name

# Test error reporting
curl -X POST -H "Content-Type: application/json" \
-d '{
"error": {"message": "Test error", "type": "Error", "source": "test"},
"attribution": {"moduleId": "test", "confidence": "high", "method": "manual", "source": "test"},
"foundry": {"version": "12.331"},
"meta": {"timestamp": "2025-06-04T00:00:00.000Z", "privacyLevel": "minimal", "reporterVersion": "0.1.2"}
}' \
https://your-endpoint.com/report/your-name

Deployment

Cloudflare Workers

  1. Install Wrangler CLI: npm install -g wrangler
  2. Login: wrangler login
  3. Configure wrangler.toml
  4. Deploy: wrangler deploy

Traditional Hosting

  1. Deploy your server code to your hosting provider
  2. Configure SSL/TLS certificate (HTTPS required)
  3. Set up monitoring and logging
  4. Configure rate limiting and security measures

Monitoring and Alerting

Error Tracking

  • Sentry: Production-ready error tracking with alerting
  • LogRocket: Session replay and error monitoring
  • Bugsnag: Error monitoring with release tracking
  • Custom: Build your own with databases and notification systems

Health Monitoring

  • Uptime Monitoring: Services like Pingdom, UptimeRobot
  • Performance Monitoring: Response time and throughput tracking
  • Log Analysis: Structured logging and analysis tools

Next Steps

  1. Integration Guide - Configure your module to use your endpoint
  2. Privacy Guidelines - Ensure compliance with privacy regulations
  3. API Reference - Complete API documentation