> ## Documentation Index
> Fetch the complete documentation index at: https://docs.edplay.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook Events and Payloads

> Every event Edplay can send, the headers on each request, and the exact JSON shape your endpoint receives.

## Events

Select the events you want under **Workspace Settings** > **Integrations** > **Webhooks** > **Events**. You must subscribe to at least one.

| Event                     | Fires when                                                                                                                    |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `course.completed`        | A learner finishes a course. Sent once, when the course first reaches the completed state.                                    |
| `course.progress_updated` | A learner's progress through a course changes. Fires repeatedly as they work through the material, so expect these in volume. |

There is also `webhook.test`, sent only when you click **Send test event**. Learner activity never triggers it.

<Warning>
  The test payload has a different shape from a real event. If your handler branches on `event`, give `webhook.test` an explicit case or ignore it. See [The test payload](#the-test-payload).
</Warning>

## Request headers

Every delivery is a `POST` with these headers:

| Header         | Value                                                                                                  |
| -------------- | ------------------------------------------------------------------------------------------------------ |
| `Signature`    | HMAC-SHA256 of the raw request body, hex encoded. See [Verify signatures](/webhooks/verify-signature). |
| `Timestamp`    | Unix timestamp of when Edplay sent the request. **Not** covered by the signature.                      |
| `Content-Type` | `application/json`                                                                                     |

## Event payload

```json theme={null}
{
  "event": "course.completed",
  "occurred_at": "2026-09-18T14:32:05+00:00",
  "workspace_id": 42,
  "user": {
    "id": 1337,
    "email": "learner@example.com",
    "name": "Alex Moreau"
  },
  "course": {
    "id": 88,
    "name": "Workplace Safety Basics"
  },
  "progress": {
    "status": "completed",
    "percentage": 100,
    "completed_at": "2026-09-18T14:32:05+00:00"
  }
}
```

`course.completed` and `course.progress_updated` share this shape. Only `event` and the `progress` values differ.

## Field reference

| Field                   | Type           | Notes                                                                           |
| ----------------------- | -------------- | ------------------------------------------------------------------------------- |
| `event`                 | string         | `course.completed`, `course.progress_updated`, or `webhook.test`.               |
| `occurred_at`           | string         | ISO 8601 with offset. When the event happened, not when Edplay sent it.         |
| `workspace_id`          | integer        | The Edplay workspace the event belongs to.                                      |
| `user.id`               | integer        | Stable Edplay user id. Use this as your join key, not the email.                |
| `user.email`            | string         | May change if the learner updates their profile.                                |
| `user.name`             | string         | Display name.                                                                   |
| `course.id`             | integer        | Stable Edplay course id.                                                        |
| `course.name`           | string         | Course title at the time of the event.                                          |
| `progress.status`       | string or null | `in_progress`, `completed`, `failed`, or `null` when no progress record exists. |
| `progress.percentage`   | integer        | `0` to `100`. Defaults to `0` when unknown.                                     |
| `progress.completed_at` | string or null | ISO 8601, or `null` if the course is not yet complete.                          |

<Note>
  `progress.status` can be `null`, and `progress.completed_at` is `null` on every `course.progress_updated` that has not reached completion. Handle both cases.
</Note>

## The test payload

The test event carries a flat `message` plus a nested `sample` object, rather than the top-level `user`, `course`, and `progress` of a real event.

```json theme={null}
{
  "event": "webhook.test",
  "occurred_at": "2026-09-18T14:32:05+00:00",
  "workspace_id": 42,
  "message": "This is a test delivery from Edplay.",
  "sample": {
    "user": { "id": 0, "email": "test@example.com", "name": "Test Learner" },
    "course": { "id": 0, "name": "Sample Course" },
    "progress": { "status": "completed", "percentage": 100, "completed_at": "2026-09-18T14:32:05+00:00" }
  }
}
```

<Warning>
  The `sample` object contains placeholder values such as `id: 0` and `test@example.com`. Never write it to your database.
</Warning>

Next: [Verify signatures](/webhooks/verify-signature)
