> For the complete documentation index, see [llms.txt](https://docs.caf.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.caf.io/caf-api/connect/webhook/request.md).

# 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 Certta

{% content-ref url="/pages/JQwSeXFXXwTy7gvEXbRC" %}
[Signature](/caf-api/connect/webhook/signature.md)
{% endcontent-ref %}

* The request body is a `JSON` that follows the CloudEvents standard

{% content-ref url="/pages/kPsx8BsX9SHJ8wfKntpH" %}
[Events](/caf-api/connect/webhook/events.md)
{% endcontent-ref %}

## Request structure

Each webhook request includes:

### Headers

| Header            | Description                                             |
| ----------------- | ------------------------------------------------------- |
| `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:

```json
{
  "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"`:

```bash
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"
  }
}'
```

{% hint style="warning" %}
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.
{% endhint %}

## What should I respond to the webhook?

{% hint style="info" %}
Our webhook considers responses with a `2xx` code (preferably `202 ACCEPTED`) within 2 seconds to mean that the integration has successfully received the event, and therefore no more calls will be made for that event. The response body is ignored by the system, except for internal audit purposes in case of delivery failures.
{% endhint %}

## 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.

```json
{
  "error": "error message"
}
```

{% hint style="danger" %}
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 Certta'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.
{% endhint %}

## Handling webhook requests

### Idempotency

{% hint style="info" %}
Webhook requests may be delivered more than once in rare cases. To handle this, implement idempotency by:

1. Using the `id` field in the event payload to detect duplicates
2. Storing processed event IDs to avoid processing the same event twice
3. Making your event handling logic idempotent (safe to run multiple times)
   {% endhint %}

### Example webhook handler

Here's a simple example of a webhook handler in Node.js Express:

```javascript
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 'TRANSACTIONPROCESSSTARTEDEVENT':
      handleTransactionProcessStarted(payload.data);
      break;
    case 'TRANSACTIONDOCUMENTSCOPYREQUESTEDEVENT':
      handleTransactionDocumentsCopyRequested(payload.data);
      break;
    case 'TRANSACTIONSTATUSUPDATEDEVENT':
      handleTransactionUpdated(payload.data);
      break;
    case 'PROFILEUPDATEDEVENT':
      handleProfileUpdated(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

{% hint style="danger" %}
**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**

* Certta 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.
  {% endhint %}

### 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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.caf.io/caf-api/connect/webhook/request.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
