Beta Program - 3 Months Free Business!

Using KirokuForms Webhooks

Automate your workflows and build powerful integrations by subscribing to real-time events from KirokuForms, such as new form submissions.

What are Webhooks?

Webhooks are a way for KirokuForms to send real-time notifications to your application when specific events occur. Instead of you repeatedly asking our API if something new has happened (polling), KirokuForms will proactively send an HTTP POST request with event data (a JSON payload) to a URL you provide.

Common use cases for webhooks include:

  • Triggering custom email or SMS notifications upon a new form submission.
  • Updating customer records in your CRM instantly.
  • Syncing submission data with your internal databases or spreadsheets.
  • Starting backend processes or workflows (e.g., order fulfillment, support ticket creation).

Available Webhook Events

You can configure your webhook endpoints to subscribe to one or more event types. The primary event type for forms is:

  • form.submission.new: Triggered immediately after a new submission is successfully received for the subscribed form.
  • (Future events such as form.updated or specific HITL events accessible via general webhooks might be added. Always check your KirokuForms dashboard or the latest API documentation for a current list.)

The events array in the webhook creation payload (see API setup below) specifies which events your endpoint will receive.

Setting Up Webhooks

You can manage webhooks for your forms through the KirokuForms dashboard or programmatically using our Management API.

Method 1: Using the KirokuForms Dashboard

  1. Log into your KirokuForms account and navigate to the "My Forms" page.
  2. Select the form for which you want to configure a webhook.
  3. Go to the form's "Settings" tab, and find the "Webhooks" or "Integrations" section.
  4. Click "Add Webhook" or "Create New Webhook".
  5. You'll typically configure the following:
    • Endpoint URL: Your server's publicly accessible HTTPS URL that will listen for incoming webhook POST requests from KirokuForms.
    • Event(s): Select the specific events you want this webhook to subscribe to (e.g., "New Submission").
    • Secret (Highly Recommended): A strong, unique string that KirokuForms will use to sign webhook requests. You'll use this same secret on your server to verify the authenticity of incoming requests. KirokuForms may auto-generate one for you if left blank, or you can provide your own. Store this secret securely.
    • Enable/Disable: A toggle to activate or deactivate the webhook.
  6. Save your configuration. KirokuForms might send a test ping or require endpoint validation depending on the setup.

(Consider adding a screenshot here showing the webhook configuration UI in your dashboard.)

Method 2: Using the KirokuForms API

For automated setup or integration into your own systems, you can manage webhooks via our API. Refer to the API Reference for complete endpoint details, including parameters for listing, updating, and deleting webhooks.

To create a webhook, you'll make a POST request to /api/forms/{formId}/webhooks. Here's an example using cURL:

Create Webhook via API
# Create a Webhook via API
# POST /api/forms/YOUR_FORM_ID/webhooks
# Requires API Key with appropriate scopes.

curl -X POST "https://www.kirokuforms.com/api/forms/YOUR_FORM_ID/webhooks" \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
           "url": "https://your-server.com/your-webhook-listener-path",
           "events": ["form.submission.new"],
           "secret": "a-very-strong-secret-you-generate-and-store",
           "isEnabled": true
         }'

Replace YOUR_FORM_ID, YOUR_API_KEY (with forms:webhooks:manage or similar scope), and https://your-server.com/your-webhook-listener-path with your actual values. The secret should be a cryptographically strong random string that you generate and store securely on your server.

Understanding the Webhook Payload

When a subscribed event occurs, KirokuForms will send an HTTP POST request with a Content-Type: application/json header to your configured Endpoint URL. The request body will be a JSON object. For a form.submission.new event, the payload will look similar to this:

Webhook Payload Example
{
  "eventType": "form.submission.new",
  "formId": "frm_abc123xyz789",
  "submissionId": "sub_def456uvw012",
  "timestamp": "2025-07-15T10:30:00Z",
  "data": {
    "name": "Arthur Dent",
    "email": "arthur.dent@example.com",
    "feedback_topic": "General Inquiry",
    "message": "What is the answer to life, the universe, and everything?"
  },
  "metadata": {
    "submittedVia": "web",
    "ipAddress": "192.0.2.42",
    "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
  }
}

Key fields in the payload:

  • eventType: Identifies the event (e.g., "form.submission.new").
  • formId: The ID of the KirokuForm that triggered the event.
  • submissionId: For submission-related events, the unique ID of the submission.
  • timestamp: An ISO 8601 formatted string indicating when the event occurred on KirokuForms' servers.
  • data: An object containing the actual data related to the event. For form.submission.new, this object holds the submitted form field names and their values.
  • metadata: (Optional) Contains additional context about the event, like IP address or user agent, if captured and configured for inclusion.

Securing Your Webhooks (Essential)

Since your webhook endpoint URL is publicly accessible, it's critical to verify that incoming requests genuinely originate from KirokuForms and haven't been tampered with. Here's how:

  1. Use HTTPS for Your Endpoint URL:

    Always use an https:// URL for your webhook listener. This encrypts the webhook payload in transit, protecting sensitive data.

  2. Verify Signatures using Your Webhook Secret:

    When you create a webhook in KirokuForms, you should configure a strong, unique "Secret". KirokuForms uses this secret to generate a cryptographic signature for each webhook request it sends. This signature is included in an HTTP header, typically named something like KirokuForms-Signature.

    The signature header often contains a timestamp (e.g., t=1678886400) and one or more signature versions (e.g., v1=a1b2c3d4...). The v1 signature is commonly an hash.

    Your webhook listener endpoint must perform the following steps to verify the signature:

    • Extract the timestamp (t= value) and the provided signature (v1= value) from the KirokuForms-Signature header.
    • Important: You need the raw, unparsed request body as a string to verify the signature. If you're using a framework like Express that automatically parses JSON, ensure you have middleware to capture the raw body before JSON parsing.
    • Construct the "string-to-sign". This is typically formed by concatenating the timestamp, a literal dot (.), and the raw request body string. (e.g., timestamp_value + "." + raw_request_body_string).
    • Calculate the expected signature by computing an HMAC-SHA256 hash of the "string-to-sign" using your stored webhook secret as the key.
    • Compare the signature you calculated with the v1 signature provided in the header. Use a timing-safe comparison method to prevent timing attacks.
    • Additionally, check if the timestamp from the header is recent (e.g., within the last 5 minutes) to protect against replay attacks.
    • If the signature is valid and the timestamp is current, you can trust the request. Otherwise, reject it (e.g., with a 400 Bad Request or 401 Unauthorized status).

    Here's a conceptual Node.js example for signature verification (ensure the header name and string-to-sign construction matches KirokuForms' exact implementation):

    Webhook Signature Verification
    const crypto = require('crypto');
    
    /**
     * Verifies a KirokuForms webhook signature.
     * @param {string} rawRequestBody - The raw, unparsed request body string.
     * @param {string} signatureHeader - The value of the 'KirokuForms-Signature' HTTP header.
     * @param {string} webhookSecret - Your configured webhook secret.
     * @param {number} toleranceSeconds - Timestamp tolerance in seconds to prevent replay attacks.
     * @returns {boolean} True if the signature is valid, false otherwise.
     */
    function verifyKirokuSignature(rawRequestBody, signatureHeader, webhookSecret, toleranceSeconds = 300) {
      if (!signatureHeader) {
        console.error('Signature verification failed: Missing KirokuForms-Signature header.');
        return false;
      }
    
      const parts = signatureHeader.split(',');
      const timestampPart = parts.find(part => part.startsWith('t='));
      const signatureV1Part = parts.find(part => part.startsWith('v1='));
    
      if (!timestampPart || !signatureV1Part) {
        console.error('Signature verification failed: Invalid signature header format.');
        return false;
      }
    
      const requestTimestamp = parseInt(timestampPart.split('=')[1], 10);
      const providedSignature = signatureV1Part.split('=')[1];
    
      if (isNaN(requestTimestamp)) {
        console.error('Signature verification failed: Invalid timestamp in signature header.');
        return false;
      }
    
      // Check timestamp tolerance (e.g., 5 minutes = 300 seconds)
      const currentTimestampSeconds = Math.floor(Date.now() / 1000);
      if (Math.abs(currentTimestampSeconds - requestTimestamp) > toleranceSeconds) {
        console.warn('Webhook timestamp is outside the tolerance window (possible replay attack). Difference:', Math.abs(currentTimestampSeconds - requestTimestamp), 'seconds.');
        return false;
      }
    
      const stringToSign = requestTimestamp + '.' + rawRequestBody;
      
      const expectedSignature = crypto
        .createHmac('sha256', webhookSecret)
        .update(stringToSign)
        .digest('hex');
    
      if (crypto.timingSafeEqual(Buffer.from(providedSignature, 'hex'), Buffer.from(expectedSignature, 'hex'))) {
        return true;
      } else {
        console.warn('Signature verification failed: Computed signature does not match provided signature.');
        return false;
      }
    }
    
    // --- Example usage in an Express route ---
    // Ensure you use a middleware to get the raw body *before* any JSON parsing middleware.
    // For Express, you might need something like:
    // app.use('/webhook-listener-path', express.raw({ type: 'application/json' }));
    //
    // app.post('/webhook-listener-path', (req, res) => {
    //   const kirokuSignature = req.headers['kirokuforms-signature']; // Or your actual header name
    //   const webhookSecret = process.env.KIROKU_FORM_WEBHOOK_SECRET; // Store securely
    //
    //   // req.body will be a Buffer if using express.raw()
    //   const rawBody = req.body.toString('utf8'); 
    //
    //   if (verifyKirokuSignature(rawBody, kirokuSignature, webhookSecret)) {
    //     const eventPayload = JSON.parse(rawBody); // Now parse the JSON
    //     console.log('Webhook verified! Processing event:', eventPayload.eventType);
    //     // Process eventPayload.data
    //     res.status(200).send({ received: true });
    //   } else {
    //     console.warn('Webhook verification failed or invalid signature.');
    //     res.status(400).send('Invalid signature.');
    //   }
    // });
  3. Respond Quickly to Acknowledge Receipt:

    Your endpoint should process the request and return a 2xx HTTP status code (e.g., 200 OK or 204 No Content) as quickly as possible (ideally within 2-5 seconds). If your application needs to perform long-running tasks based on the webhook (like making further API calls, sending emails, complex database operations), do so asynchronously after sending the 2xx response. Add the webhook data to a queue or trigger a background job. This prevents timeouts on KirokuForms' side and ensures reliable webhook delivery.

Building a Webhook Listener (Node.js/Express Example)

This example provides a basic structure for a webhook listener using Node.js and the Express framework. It demonstrates receiving a webhook, (conceptually) verifying its signature, and processing a form.submission.new event.

Complete Webhook Listener Example
const express = require('express');
const crypto = require('crypto'); // For signature verification

const app = express();
const port = process.env.PORT || 3000;

// Store your webhook secrets securely (e.g., in environment variables)
// Map form IDs to their secrets if you have multiple webhooks/secrets
const WEBHOOK_SECRETS = {
  'YOUR_FORM_ID_1': 'YOUR_SECRET_FOR_FORM_1',
  'YOUR_FORM_ID_2': 'YOUR_SECRET_FOR_FORM_2'
};

// Middleware to get the raw request body for signature verification,
// then parse JSON if signature is valid.
app.use('/your-webhook-listener-path', (req, res, next) => {
  let rawBody = '';
  req.on('data', chunk => {
    rawBody += chunk.toString();
  });
  req.on('end', () => {
    req.rawBody = rawBody; // Attach raw body for signature verification
    try {
      // Attempt to parse JSON after getting raw body, but verification uses rawBody
      req.body = JSON.parse(rawBody || '{}'); 
    } catch (e) {
      console.error("Webhook payload is not valid JSON:", e.message);
      req.body = {}; // Set to empty object if parsing fails
    }
    next();
  });
});

// Your verifyKirokuSignature function from the example above would go here
// function verifyKirokuSignature(rawRequestBody, signatureHeader, webhookSecret, toleranceSeconds = 300) { ... }

app.post('/your-webhook-listener-path', (req, res) => {
  const formId = req.body.formId; // Assuming formId is in the payload
  const webhookSecret = formId ? WEBHOOK_SECRETS[formId] : null;
  const signature = req.headers['kirokuforms-signature']; // Adjust if header name is different

  // ** 1. Verify the signature (CRITICAL) **
  // if (!webhookSecret || !verifyKirokuSignature(req.rawBody, signature, webhookSecret)) {
  //   console.warn('Webhook signature verification failed for form ' + (formId || 'unknown') + '.');
  //   return res.status(400).send('Signature verification failed.');
  // }
  // console.log('Webhook signature VERIFIED for form ' + formId);

  // ** 2. Process the event **
  const { eventType, data, submissionId, timestamp } = req.body;
  console.log('Received event: ' + eventType + ' at ' + timestamp);

  switch (eventType) {
    case 'form.submission.new':
      console.log('New submission (' + submissionId + ') for form ' + formId + ':', data);
      // Add your business logic here:
      // - Save to your database
      // - Send a custom email
      // - Update a CRM
      // - Trigger a Slack notification
      break;
    // Add other event types if KirokuForms supports them
    // case 'form.updated':
    //   console.log('Form ' + formId + ' was updated.');
    //   break;
    default:
      console.log('Received unhandled event type: ' + eventType);
  }

  // ** 3. Acknowledge receipt quickly **
  res.status(200).json({ message: 'Webhook received successfully.' });
});

// app.listen(port, () => {
//   console.log('KirokuForms webhook listener running at http://localhost:' + port + '/your-webhook-listener-path');
//   console.log('Ensure this URL is publicly accessible and configured in KirokuForms.');
// });

To run this example, you would save it as a .js file, install Express (npm install express), replace placeholders with your actual webhook secret and logic, and run it (e.g., node your-listener-file.js). Your server would need to be publicly accessible via HTTPS for KirokuForms to reach it.

Delivery Attempts, Retries, and Error Handling

  • Acknowledgment: Your webhook endpoint MUST return a 2xx HTTP status code to signal successful receipt. Any other status code (or a timeout) will be considered a delivery failure by KirokuForms.
  • Retries: If KirokuForms doesn't receive a 2xx response, it will typically attempt to resend the webhook. This often follows an exponential backoff schedule (e.g., retrying after 1 minute, then 5 minutes, then 15 minutes, up to a certain number of attempts or duration).
  • Disabling Webhooks: If an endpoint consistently fails to respond successfully after multiple retries, KirokuForms may automatically disable the webhook to prevent further issues. You would usually receive an email notification if this happens and would need to re-enable it via the dashboard or API after fixing your listener.
  • : Because of retries (and other potential network issues), your listener might occasionally receive the same webhook event more than once. Design your event processing logic to be idempotent – meaning processing the same event multiple times has the same effect as processing it once. This can be achieved by checking for unique event IDs or submission IDs before taking action.

Troubleshooting Common Issues

  • Webhooks Not Arriving:
    • Is your endpoint URL correct in KirokuForms and publicly accessible via HTTPS? Use a tool like webhook.site to test if your endpoint can receive POST requests from the internet.
    • Is the webhook enabled in KirokuForms?
    • Check your server logs for any incoming requests or errors.
    • Are there any firewalls (server-side or network) blocking requests from KirokuForms' IP addresses?
  • Signature Verification Failing:
    • Ensure the webhook secret stored on your server exactly matches the one configured for that webhook in KirokuForms. Even a small difference will cause failure.
    • Confirm you are using the correct raw request body (unparsed) for generating the signature. JSON parsing middleware can alter the body.
    • Double-check the algorithm (e.g., HMAC-SHA256) and the exact construction of the string-to-sign (timestamp, dot, body).
  • Endpoint Responding with Errors (4xx or 5xx):
    • Thoroughly debug your listener code. Check server logs for detailed error messages.
    • Remember to send a 2xx response quickly and process data asynchronously if needed.
  • Using KirokuForms Logs:
    • Check the webhook settings area in your KirokuForms dashboard. There might be delivery logs showing the status of recent attempts, response codes received from your server, and any error messages.

Next Steps

With webhooks configured, you can build powerful, event-driven integrations.