Skip to main content

Idempotency

Use the event id and resource resourceId for idempotent processing:
const processedEvents = new Set();

function processEvent(event) {
  // Check if event already processed
  if (processedEvents.has(event.id)) {
    return;
  }
  
  // Check if resource already exists in your system
  const existingResource = getResourceById(event.data.resourceId);
  if (existingResource && event.type.endsWith('.created')) {
    return; // Already exists, skip
  }
  
  // Process the event
  handleEvent(event);
  processedEvents.add(event.id);
}

Error Handling

Always return appropriate HTTP status codes:
app.post('/webhooks/tiro', (req, res) => {
  try {
    const event = req.body;
    
    // Process the event
    processEvent(event);
    res.status(200).send('OK');
  } catch (error) {
    console.error('Webhook processing error:', error);
    res.status(500).send('Internal server error');
  }
});

Retry Handling

Our webhook system will retry failed deliveries. Handle retries gracefully:
  • Return 2xx status codes for successful processing
  • Return 4xx status codes for permanent failures (we won’t retry)
  • Return 5xx status codes for temporary failures (we will retry)

Security

Always verify webhook authenticity:
function verifyWebhookAuth(authHeader, expectedSecret) {
  const token = authHeader.replace('Bearer ', '');
  return token === expectedSecret;
}

app.post('/webhooks/tiro', (req, res) => {
  const authHeader = req.headers.authorization;
  
  if (!verifyWebhookAuth(authHeader, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Unauthorized');
  }
  
  // Process webhook event
  processEvent(req.body);
  res.status(200).send('OK');
});

Resource Routing

Use resourceType and resourceId for efficient routing:
function routeEvent(event) {
  const { resourceType, resourceId } = event.data;
  
  switch (resourceType) {
    case 'Note':
      return handleNoteEvent(event.type, resourceId, event.data.resource);
    case 'Paragraph':
      return handleParagraphEvent(event.type, resourceId, event.data.resource);
    default:
      console.log('Unknown resource type:', resourceType);
  }
}