Webhooks

Receive real-time notifications when events occur in your forms. Perfect for integrating with automation tools, sending data to your backend, or triggering custom workflows.

What are Webhooks?

Webhooks are automated messages sent from WorkForm to your server when specific events happen. Instead of constantly polling our API for new responses, webhooks push data to you in real-time.

Common Use Cases

  • Send form submissions to your CRM or database
  • Trigger email notifications when forms are completed
  • Sync responses with external systems (Airtable, Google Sheets, etc.)
  • Start automated workflows in Zapier or Make
  • Update analytics dashboards in real-time
Supported Events
form.submitted
Form Submission

Triggered when a user successfully submits a form. Includes all response data and metadata.

form.response.updated
Response Updated

Triggered when an existing response is modified (e.g., via admin panel or API).

form.published
Form Published

Triggered when a form is published or made public.

form.unpublished
Form Unpublished

Triggered when a form is unpublished or made private.

Creating a Webhook

Method 1: Using the Dashboard

  1. Navigate to your Workspace Settings
  2. Go to the "Webhooks" section
  3. Click "Create New Webhook"
  4. Enter your webhook URL (must be HTTPS)
  5. Select which events you want to receive
  6. Choose specific forms or listen to all forms
  7. Save and activate your webhook

Method 2: Using the API

POST
/api/v1/webhooks
// Request
POST /api/v1/webhooks
Content-Type: application/json
X-API-Key: workform_your_api_key

{
  "url": "https://your-server.com/webhook",
  "events": ["form.submitted", "form.response.updated"],
  "description": "Send submissions to CRM",
  "isActive": true,
  "formId": "abc123" // Optional: specific form
}

// Response
{
  "success": true,
  "message": "Webhook created successfully.",
  "data": {
    "id": "wh_abc123",
    "url": "https://your-server.com/webhook",
    "events": ["form.submitted", "form.response.updated"],
    "description": "Send submissions to CRM",
    "isActive": true,
    "secret": "whsec_...", // Use for signature verification
    "createdAt": "2024-01-01T00:00:00.000Z"
  }
}
Webhook Payload Structure

When an event occurs, WorkForm sends an HTTP POST request to your webhook URL with the following structure:

{
  "event": "form.submitted",
  "timestamp": "2024-01-01T12:00:00.000Z",
  "webhookId": "wh_abc123",
  "data": {
    "formId": "abc123",
    "formName": "Contact Form",
    "responseId": "resp_xyz789",
    "submittedAt": "2024-01-01T12:00:00.000Z",
    "responses": {
      "email_q1": "user@example.com",
      "name_q2": "John Doe",
      "message_q3": "I'd like to discuss a project"
    },
    "questionMetadata": {
      "email_q1": {
        "id": "email_q1",
        "title": "Email Address",
        "description": "Please provide your email",
        "type": "email"
      },
      "name_q2": {
        "id": "name_q2",
        "title": "Full Name",
        "description": "",
        "type": "short-text"
      },
      "message_q3": {
        "id": "message_q3",
        "title": "Your Message",
        "description": "Tell us how we can help",
        "type": "long-text"
      }
    },
    "deviceType": "Desktop",
    "browser": {
      "name": "Chrome",
      "version": "120.0.0",
      "os": "macOS 14.0"
    },
    "completionTime": 120
  }
}
Security & Verification

Request Signing

All webhook requests include a signature in the X-WorkForm-Signature header. Use this to verify the request came from WorkForm.

// Example: Verifying webhook signature (Node.js)
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');
    
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// In your webhook handler
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-workform-signature'];
  const secret = 'whsec_...'; // From webhook creation
  
  if (!verifyWebhookSignature(req.body, signature, secret)) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process the webhook
  console.log('Event:', req.body.event);
  console.log('Data:', req.body.data);
  
  res.status(200).send('OK');
});

Security Best Practices

  • Always verify webhook signatures before processing
  • Use HTTPS endpoints only (HTTP will be rejected)
  • Store webhook secrets securely (use environment variables)
  • Implement rate limiting on your webhook endpoint
  • Log all webhook requests for debugging
Delivery & Retry Logic

Delivery Expectations

  • Webhooks are delivered in near real-time (typically within 1-2 seconds)
  • Your endpoint should respond with a 2xx status code within 5 seconds
  • Any non-2xx response or timeout will trigger a retry

Automatic Retries

If delivery fails, WorkForm automatically retries with exponential backoff:

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry15 minutes
4th retry1 hour
5th retry6 hours

After 5 failed attempts, the webhook will be marked as failed and you'll receive an email notification.

Responding to Webhooks

Your webhook endpoint should:

  • Respond quickly with HTTP 200 (process asynchronously if needed)
  • Return a simple "OK" or empty response body
  • Handle duplicate events gracefully (use responseId for idempotency)
  • Queue long-running tasks for background processing
Testing Webhooks

Local Development

Use tools like ngrok or localtunnel to expose your local server for webhook testing:

# Using ngrok
ngrok http 3000

# This will give you a public URL like:
# https://abc123.ngrok.io

# Use this URL when creating your webhook:
# https://abc123.ngrok.io/webhook

Test Event Trigger

You can trigger a test event from the dashboard or API:

POST /api/v1/webhooks/wh_abc123/test
X-API-Key: workform_your_api_key

{
  "event": "form.submitted"
}

// This sends a sample payload to your webhook URL

Webhook Logs

View webhook delivery attempts, response codes, and payloads in your workspace settings under "Webhooks" → "Logs". This helps debug failed deliveries and verify your integration.

Troubleshooting

Webhook not receiving events

  • Verify webhook is active in dashboard
  • Check that your URL is accessible (HTTPS required)
  • Ensure firewall/security groups allow WorkForm IPs
  • Verify event types match what you're testing

Signature verification fails

  • Ensure you're using the correct webhook secret
  • Verify you're hashing the raw request body (not parsed JSON)
  • Check for middleware that modifies the request body

Receiving duplicate events

  • This is expected - implement idempotency using responseId
  • Store processed event IDs to prevent duplicate processing
  • Use database unique constraints where applicable

Timeouts or slow responses

  • Respond with 200 immediately, process in background
  • Use job queues (Redis, RabbitMQ) for async processing
  • Keep webhook handlers simple and fast