Request
What does the webhook send me?
The webhook sends an event via HTTP
POST
request with Content-Type: application/json
with the following parameters:
An
X-Caf-Signature
header used to verify that the request came from Caf
The request body is a
JSON
that follows the CloudEvents standard
Request structure
Each webhook request includes:
Headers
Content-Type
Always application/json
User-Agent
Identifies as Caf-Webhook/Connect
X-Caf-Signature
Contains the HMAC SHA-256 signature of the request body
Request body
The request body follows the CloudEvents format, although the specific CloudEvents HTTP headers are not included:
{
"specversion": "1.0",
"type": "COMMUNICATIONCREATEDEVENT",
"source": "COMMUNICATION",
"id": "01JZNK5ZQBNF623MB5KE64GNQB",
"time": "2025-07-08T18:01:19.622Z",
"datacontenttype": "application/json",
"data": {
"tenantId": "016e8f79-2399-4d35-90f9-c6f91b73189d",
"channel": "sms",
"externalId": "external-id",
"notificationId": "01JZNK51YCZXT55TKF8M366QHJ",
"system": "onboarding",
"occurredOn": "2025-07-08T18:00:46.797Z"
}
}
Examples
Curl
Example curl command assuming SECRET: "dummysecret"
:
curl --location 'http://localhost:8080/webhook' \
--header 'X-Caf-Signature: 6f9ed23a7b505a3b6907c5f6eb2ad1b056fbf35a643d365a9a072ed7aabca153' \
--header 'Content-Type: application/json' \
--data '{
"specversion": "1.0",
"type": "COMMUNICATIONCREATEDEVENT",
"source": "COMMUNICATION",
"id": "01JZNK5ZQBNF623MB5KE64GNQB",
"time": "2025-07-08T18:01:19.622Z",
"datacontenttype": "application/json",
"data": {
"tenantId": "016e8f79-2399-4d35-90f9-c6f91b73189d",
"channel": "sms",
"externalId": "external-id",
"notificationId": "01JZNK51YCZXT55TKF8M366QHJ",
"system": "onboarding",
"occurredOn": "2025-07-08T18:00:46.797Z"
}
}'
It's important to validate the signature of the payload as soon as it arrives (as a byte array), without any parsing of the information. This ensures the integrity of the verification.
Example of a request with an equivalent payload, just with some extra spaces that affect the signature, but equally valid:
curl --location 'http://localhost:8080/webhook' \
--header 'X-Caf-Signature: cf7e092c9148a48f5ee5f12b947f46b331eac6bf0745e1e1d0f3df722e219df3' \
--header 'Content-Type: application/json' \
--data '{ "specversion": "1.0", "type": "COMMUNICATIONCREATEDEVENT", "source": "COMMUNICATION", "id": "01JZNK5ZQBNF623MB5KE64GNQB", "time": "2025-07-08T18:01:19.622Z", "datacontenttype": "application/json", "data": { "tenantId": "016e8f79-2399-4d35-90f9-c6f91b73189d", "channel": "sms", "externalId": "external-id", "notificationId": "01JZNK51YCZXT55TKF8M366QHJ", "system": "onboarding", "occurredOn": "2025-07-08T18:00:46.797Z" } }'
What should I respond to the webhook?
Error cases
The webhook request has the purpose of successfully integrating the event and nothing more than that. With this purpose in mind, error responses should only be used to indicate failure in the event integration (by integrated event, it means the event was successfully received by the webhook server).
The webhook delivery mechanism accepts and recognizes errors within the HTTP 5xx error series, which can indicate errors in receiving or processing the request by the server. Delivery retries will happen only for this class of errors.
Error responses can follow the payload specified below to detail and make clear the reason for the error in our internal audit. Any other fields and/or formats will be ignored.
{
"error": "error message"
}
If all delivery attempts fail, the webhook discards the event, which will no longer be delivered via webhook!
The maximum number of attempts, the interval between each delivery attempt, and the time for timeout are at Caf.io's discretion. Currently, we consider requests that take more than 2 seconds to respond as timeout and try to resend the events for up to 15 minutes.
Handling webhook requests
Idempotency
Example webhook handler
Here's a simple example of a webhook handler in Node.js Express:
const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('crypto');
const app = express();
app.use(bodyParser.json());
app.post('/webhook', (req, res) => {
const sigHeader = req.headers['x-caf-signature'];
const payload = req.body;
// Verify the signature
if (!verifySignature(payload, sigHeader, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process the CloudEvent based on its type
const eventType = payload.type;
const eventSource = payload.source;
const eventId = payload.id;
console.log(`Processing CloudEvent: ${eventId} from ${eventSource} of type ${eventType}`);
switch (eventType) {
case 'COMMUNICATIONCREATEDEVENT':
handleCommunicationCreated(payload.data);
break;
case 'TRANSACTIONUPDATEDEVENT':
handleTransactionUpdated(payload.data);
break;
case 'PROFILECREATEDEVENT':
handleProfileCreated(payload.data);
break;
// Handle other event types...
default:
console.log(`Unhandled event type: ${eventType}`);
}
// Respond with success
res.status(200).send('Event received');
});
// Start the server
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});
Troubleshooting
Common issues
401 Unauthorized
Your endpoint returned a 401 code.
Verify that you are correctly validating signatures.
Timeout
Your endpoint took too long to respond.
Optimize your code to respond in less than 2 seconds.
Connection Refused
Caf.io couldn't connect to your endpoint.
Check if your server is running and accessible.
Signature Rejection
Failed webhook signature validation.
Verify that you are using the correct secret and validating the raw body bytes.
Logs and debugging
You can use webhook logs in Trust, the platform that manages configurations, to view the delivery status of recent events and any error messages. The logs maintain a history of:
Successful and failed delivery attempts
HTTP response codes received
Delivery timestamps
Specific errors encountered during deliveries
Last updated