# Webhooks

> Automatic notices to your system when something happens on the platform: events, filters, personal data, delivery history and what to do when it fails.

A webhook tells your system when something happens on the platform. For example: a student completed a lesson, and Cademí sends a notice to your CRM. The webhook is the address on your system (or on Zapier, Make, n8n) that receives these notices. You set it all up in **Settings > Webhooks**. The format of each event, for whoever will program the receiver, is at https://cademi.dev/webhooks.

## The idea in one minute

- An **event** is a fact Cademí records in the account: a student got access, passed an exam, completed a lesson.
- A **webhook** is an address subscribed to some events.
- A **delivery** is each notice Cademí sends to that address.

A webhook delivery has nothing to do with a sales delivery ([Deliveries](https://help.cademi.cloud/en/vendas/entregas.md)).

**The most important rule:** the webhook listens to access, not to the sale. A gateway sale is not an event. What reaches your system is the access the sale produced.

The screen has two tabs:

- **Endpoints**: the list of webhooks. Each row shows **Event / scope**, **Endpoint** and **Status**. The **Status** is **Active** or **Inactive**, and you switch it on the row itself. The **Events** and **Status** filters and the search by address narrow the list.
- **History**: the deliveries.

There are two webhook formats:

- **Cademí v3**, the current format. One webhook receives several events, each with its own filter, and every delivery is signed. It is what **New webhook** creates, and it is what this article explains.
- **Legacy v2** and **Legacy v1 (discontinued)**, the old format, with one event per webhook. It only serves a system that already receives this format. **New legacy webhook** creates one. For a new integration, use **New webhook**.

## Creating a webhook

1. Open **Settings > Webhooks**.
2. Click **New webhook**.
3. Under **Where to send**, paste the **Endpoint**: your system's address, starting with https://.
4. If you want, fill in the **Description**. It helps you recognize the webhook in the list and is not sent.
5. Under **When to send**, check the events.
6. For events that accept a filter, fill in the **Send only when** block, which appears right below. Empty means "whenever it happens".
7. Under **What each delivery carries**, choose the level of personal data (see below).
8. Review the **Summary**, on the side.
9. Click **Create webhook**.
10. In the **Signing secret** window, copy and keep the secret.

The secret does not appear again. With it, your server confirms that each delivery came from Cademí.

To choose the events, the screen has two tabs:

- **Student journey** groups the most used moments.
- **Sync (advanced)** lists every platform event, by group, with the technical name. It is for whoever keeps another system always in step with Cademí.

**Search event** searches both tabs. **Check all** and **Uncheck** apply per group. **View delivery example** shows what reaches your address, with sample data.

### The moments of the student journey

| Moment | Events |
|---|---|
| **Sign-up** | **Student created**, **Student tags updated**, **Custom field updated** |
| **Access** | **Access granted**, **First access**, **Access suspended or reactivated**, **Access revoked**, **Access restored** |
| **Progress** | **Course started**, **Lesson completed**, **Progress milestone reached**, **Progress updated**, **Course completed** |
| **Exams** | **Exam completed**, **Exam attempt reopened** |
| **Certificates** | **Certificate issued**, **Certificate revoked** |
| **Terms** | **Term accepted** |
| **Points** | **Points credited**, **Points adjusted** |
| **Support** | **Ticket opened**, **Ticket replied**, **Ticket closed**, **Question asked**, **Question answered**, **Comment posted**, **Comment replied** |

**Attention: this is where most mistakes happen.** Each event has its own moment:

- Sale: the student receives a delivery and fires **Access granted**. Refund, late payment and payment coming back fire **Access suspended or reactivated**.
- **Course started** is the first completed lesson, not the access granted. For a welcome, use **Student created** or **First access**.
- **Exam completed** fires on pass or fail. For only one of them, filter by **Exam result**.
- Reaching 100% does not issue a certificate. **Certificate issued** only happens when someone requests the certificate.
- The notice for a comment or a question does not carry the text the student wrote.
- An administrator who views the area as a test student generates none of these events.

### The filters

| Filter | On the events for | What it does |
|---|---|---|
| **Product** | progress, exams, certificates, comments and questions | A single product, or **Any product** |
| **Lesson** and **Exam** | lesson completed, comments, questions and exams | They appear after you choose the product. You can check several |
| **Exam result** | **Exam completed** | **Passed or failed**, **Passed only** or **Failed only** |
| **On reaching** | **Progress milestone reached** | 50%, 75% or 90% |
| **From** | **Progress updated** | A minimum percentage. Once the student passes it, every following lesson fires again. For a one-time notice, prefer the milestone |
| **Sign-up type** | **Student created** | **Paid sign-up** or **Free sign-up**. Sign-up with Google and sign-up through import are neither. With either choice, they are left out |
| **Offer** | **Access granted** | Only the accesses from that delivery: the buyers of this offer |
| **Tag added to the student** | **Student tags updated** | Only when that tag is added. A tag the student already had, or that was only removed, does not fire |
| **Custom field** and **Only when filled in** | **Custom field updated** | One field. If you want, only when it stops being empty |
| **Term** | **Term accepted** | A specific term |
| **Points for** | **Points credited** | The reason for the points (lesson, milestone, exam, comment) |

### How much personal data goes

The level applies to every event of the same webhook.

| Level | What arrives | Use it when |
|---|---|---|
| **IDs only, no personal data** (the default) | The codes of the student and of what happened, with no name or contact. Your system looks up the rest through the API | The receiver queries the student, or only needs to count |
| **With personal data masked** | The codes, plus name, email and phone partly hidden. It is still personal data | The tool needs a hint of who it is, without storing the real contact |
| **Full, with personal data** | The data as it is in the student's record | The system needs to write to, call or bill the student. And it can be trusted to store that data |

Only an administrator authorized to see student data can choose the full level. If the webhook includes tickets, they must also see support. Cademí checks each delivery against whoever created the webhook. If that person loses the authorization, deliveries go out masked until it comes back, and the screen warns you.

## A webhook's page

Click the row to open the webhook. At the top are the status, **View this webhook history**, **Send test** and **Generate new secret**.

The health panel shows:

- the **Last delivered**;
- the **Last 24 hours**: delivered, failed and queued;
- the state of the **Address**: **Receiving normally**, **Deliveries paused after repeated failures** or **Testing the address again**.

**Send test** sends a sample of the chosen event to your address right away. The sample has fictitious data, is signed with the current secret and is marked as a test. You see the **HTTP status**, the **Time** and the **Response**. The test does not go into the history and does not count toward the webhook's health.

**Generate new secret** creates a new secret and shows it once. The previous one keeps signing for 24 hours. This way, you switch the secret in the receiver without downtime.

## History

The **History** tab lists deliveries from the last 60 days, in four views: **All**, **Success**, **Pending** and **Failed**. Only **Failed** has a count, because it is the queue that calls for action. The **Webhook** filter narrows by destination.

Each row shows **Sent at**, **Event**, **Code** (the destination's response), **Endpoint** and **Status**. If there was more than one attempt, the row shows the number.

| Status | What it means |
|---|---|
| **Queued** | Waiting for the next send |
| **Sending** | Going out right away |
| **Success** | The destination received it and responded with success |
| **Error** | The destination refused in a way that retrying won't fix (address that doesn't exist or is blocked). It only goes out again by manual resend |
| **Exhausted** | The attempts ran out without success. It only goes out again by manual resend or when the webhook is reactivated |
| **Cancelled** | It will no longer be sent. The webhook was deactivated or deleted before it went out, or the system abandoned the address |

Click the row to open **Delivery details**. The window shows the destination's **Response** and the **Payload** sent. On failed deliveries, it has the **Resend** button. Resending puts the same delivery back in the queue. It does not create a new event.

## When the destination fails

If the address does not accept a delivery, Cademí tries again on its own:

- The wait between attempts grows from 2 minutes up to 6 hours.
- Cademí tries about 10 times, or for 72 hours.
- If the destination asks to wait, or is unstable, the postponement does not use up an attempt.
- A definitive refusal is not retried.

The path of a delivery to its final status, with the retries and the returns to the queue:

```mermaid
stateDiagram-v2
    accTitle: The path of a webhook delivery
    accDescr: When a checked event happens, the delivery enters the queue and goes out. If the destination accepts it, it ends in Success. A temporary failure sends it back to the queue for another attempt. A definitive refusal leads to Error, and running out of attempts or time leads to Exhausted. Deactivating or deleting the webhook before sending leads to Cancelled. Resend, or reactivate and resend, puts the delivery back in the queue.
    state "Queued" as Fila
    state "Sending" as Enviando
    state "Success" as Sucesso
    state "Error" as Erro
    state "Exhausted" as Esgotado
    state "Cancelled" as Cancelado
    [*] --> Fila: A checked event happens
    Fila --> Enviando
    Enviando --> Sucesso: Destination accepts
    Enviando --> Fila: Temporary failure, new attempt
    Enviando --> Erro: Definitive refusal
    Enviando --> Esgotado: Out of attempts or time
    Fila --> Cancelado: Webhook deactivated by the system or deleted
    Erro --> Fila: Resend
    Esgotado --> Fila: Resend, or reactivate and resend
    Cancelado --> Fila: Reactivate and resend
```

With the webhook **Inactive**, the delivery waits in the queue and is not cancelled. Reactivate and resend only returns what ran out or was cancelled by the deactivation in the last 48 hours. A delivery cancelled because the webhook was deleted does not come back.

| Situation | What happens | How it comes back |
|---|---|---|
| You set the webhook to **Inactive** | Deliveries are held. Waiting is not a failure | Turn it back on and they go out |
| The address fails on several different deliveries within 24 hours | The system deactivates the webhook and cancels what was still waiting. It emails the administrators who manage webhooks | On the webhook's page, **Reactivate** or **Reactivate and resend the last 48 hours** |
| The address goes a long time without responding | Every webhook pointing to that address is deactivated. This also applies in other accounts that use it | Reactivate each one |

The list and the webhook's page show the reason for the deactivation. Saving the webhook does not reactivate it. Fix the address, if needed, and use **Reactivate**. Nothing older than 48 hours comes back on its own.

## Good to know

- **The receiver must tolerate repeats.** Resends and reactivations deliver the same event again. If your system does not recognize the repeated event, a CRM creates two contacts and an email tool writes to the student twice.
- **Deleting cancels what was waiting.** Deleting a webhook cancels pending deliveries. Completed ones stay in the history. Support can recover the deleted webhook.
- **Changing the level doesn't rewrite the past.** A richer level applies to what happens afterwards. What was already prepared goes out as it was.
- **A webhook is not a history.** It can be paused and turned off. To look back or build a report, use the API's event list, not a webhook feeding a spreadsheet.
- **Diamond.** The stage messages of Diamond funnels are on the Diamond screens ([Diamond Funnels](https://help.cademi.cloud/en/conteudo/funis-diamante.md)), not here.
- **Old and new on the same system.** A legacy webhook and a v3 webhook pointed at the same tool announce the same moment twice, under two names.

## Frequently asked questions

**I want to notify my CRM on every purchase.** In the **Access** moment, check **Access granted** and **Access suspended or reactivated**. If it is a specific delivery, filter by **Offer**. The webhook listens to access, not to the sale.

**The event happened and nothing arrived.** Open the **History** and look for the delivery:

- **Queued**: the platform will still try.
- **Error** or **Exhausted**: check the destination's **Response**, fix it and resend.
- No delivery: the event is not checked, or the filter excluded the case.

If the list shows the webhook deactivated, reactivate it.

**My webhook was deactivated on its own.** The address failed on several deliveries in a row. Fix the destination and test it with **Send test**. Then click **Reactivate and resend the last 48 hours** to recover what was left behind.

**I got the same notice twice.** A resend or a reactivation delivers the same event again. Your system must recognize the repeated event.
