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
- Log into your KirokuForms account and navigate to the "My Forms" page.
- Select the form for which you want to configure a webhook.
- Go to the form's "Settings" tab, and find the "Webhooks" or "Integrations" section.
- Click "Add Webhook" or "Create New Webhook".
-
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.
- 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 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:
{
"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. Forform.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:
- 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. - 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...
). Thev1
signature is commonly anHMAC-SHA256 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 theKirokuForms-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
or401 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 Verificationconst 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.'); // } // });
The precise name of the signature header (e.g., KirokuForms-Signature
orX-Kiroku-Signature
) and the exact method for constructing the string-to-sign are critical. Always consult the KirokuForms dashboard or specific webhook setup instructions for these details if they differ from the example. - Extract the timestamp (
- Respond Quickly to Acknowledge Receipt:
Your endpoint should process the request and return a
2xx
HTTP status code (e.g.,200 OK
or204 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 the2xx
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.
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.
-
Idempotency : 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
or5xx
):- 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.
- Dive deeper into the Webhook Management API Reference.
- Explore our Code Examples for inspiration on what to build with webhooks.
- Return to the main Developer Hub.