This file contains standardized webhook examples that can be referenced across all guides to ensure consistency.

Basic Webhook Setup

Environment Configuration

# Primary webhook endpoint
WEBHOOK_URL=https://your-app.com/webhook/flatfile

# Backup/testing endpoint (optional)
WEBHOOK_TEST_URL=https://webhook.site/your-unique-id

Webhook Request Patterns

Basic POST Request with Fetch

const webhookUrl = process.env.WEBHOOK_URL || "https://your-app.com/webhook/flatfile";

const response = await fetch(webhookUrl, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "User-Agent": "Flatfile-Integration/1.0"
  },
  body: JSON.stringify({
    event: "data_ready",
    workbook: workbookData,
    timestamp: new Date().toISOString()
  })
});

if (!response.ok) {
  throw new Error(`Webhook failed: ${response.status} ${response.statusText}`);
}

POST Request with Authentication

const webhookUrl = process.env.WEBHOOK_URL;
const apiKey = await event.secrets("WEBHOOK_API_KEY");

const response = await fetch(webhookUrl, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${apiKey}`,
    "X-Flatfile-Event": event.topic
  },
  body: JSON.stringify(payload)
});

Using Axios (Alternative)

import axios from "axios";

const webhookUrl = process.env.WEBHOOK_URL;

const response = await axios.post(webhookUrl, {
  workbook,
  sheets,
  metadata: {
    source: "flatfile",
    timestamp: new Date().toISOString()
  }
}, {
  headers: {
    "Content-Type": "application/json"
  },
  timeout: 30000 // 30 second timeout
});

Complete Webhook Integration Patterns

Workbook Action with Webhook

// Blueprint configuration
actions: [
  {
    operation: "submitToWebhook",
    mode: "foreground",
    label: "Submit to External System",
    description: "Send data to your external system",
    primary: true
  }
]

// Listener implementation
listener.on(
  "job:ready",
  { job: "workbook:submitToWebhook" },
  async (event) => {
    const { jobId, workbookId } = event.context;
    
    try {
      await api.jobs.ack(jobId, {
        info: "Preparing data for submission...",
        progress: 10
      });

      // Collect workbook data
      const { data: workbook } = await api.workbooks.get(workbookId);
      const { data: sheets } = await api.sheets.list({ workbookId });
      
      const payload = {
        workbook: {
          ...workbook,
          sheets: await Promise.all(
            sheets.map(async (sheet) => {
              const { data: records } = await api.records.get(sheet.id);
              return { ...sheet, records: records.records };
            })
          )
        }
      };

      // Send to webhook
      const webhookUrl = await event.secrets("WEBHOOK_URL");
      const response = await fetch(webhookUrl, {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify(payload)
      });

      if (response.ok) {
        await api.jobs.complete(jobId, {
          outcome: {
            message: `Data successfully submitted to ${webhookUrl}`
          }
        });
      } else {
        throw new Error(`Webhook returned ${response.status}`);
      }
    } catch (error) {
      await api.jobs.fail(jobId, {
        outcome: {
          message: "Failed to submit data. Please check your webhook URL and try again."
        }
      });
    }
  }
);

Record Hook with Webhook

import { recordHook } from "@flatfile/plugin-record-hook";

listener.use(
  recordHook("contacts", async (record, event) => {
    try {
      const webhookUrl = process.env.WEBHOOK_URL || 
        await event.secrets("WEBHOOK_URL");

      const response = await fetch(webhookUrl, {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          action: "record_processed",
          record: record.toJSON(),
          sheet: event.context.sheetId
        })
      });

      if (response.ok) {
        record.addInfo("status", "Successfully sent to external system");
      } else {
        record.addError("status", "Failed to send to external system");
      }
    } catch (error) {
      record.addError("status", "Webhook communication failed");
    }

    return record;
  })
);

Webhook Testing Patterns

Using webhook.site for Testing

// For testing purposes - use webhook.site
const testWebhookUrl = "https://webhook.site/your-unique-id";

// In production, use your actual endpoint
const webhookUrl = process.env.NODE_ENV === "production" 
  ? process.env.WEBHOOK_URL 
  : testWebhookUrl;

Local Development Testing

// Use ngrok or similar for local testing
const webhookUrl = process.env.WEBHOOK_URL || 
  "https://your-ngrok-url.ngrok.io/webhook/flatfile";

Error Handling Best Practices

Comprehensive Error Handling

async function sendWebhook(url, payload, retries = 3) {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      const response = await fetch(url, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-Attempt": attempt.toString()
        },
        body: JSON.stringify(payload),
        timeout: 30000
      });

      if (response.ok) {
        return await response.json();
      }
      
      if (response.status >= 400 && response.status < 500) {
        // Client error - don't retry
        throw new Error(`Client error: ${response.status}`);
      }
      
      // Server error - retry
      if (attempt === retries) {
        throw new Error(`Server error after ${retries} attempts: ${response.status}`);
      }
      
    } catch (error) {
      if (attempt === retries) {
        throw error;
      }
      
      // Wait before retry (exponential backoff)
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
    }
  }
}

Security Considerations

  1. Always use HTTPS for webhook URLs
  2. Validate webhook signatures when possible
  3. Use authentication tokens for sensitive endpoints
  4. Implement proper error handling to avoid exposing sensitive information
  5. Set reasonable timeouts to prevent hanging requests
  6. Use environment variables for URLs and credentials