# Event Catalogue

> Every webhook event type, when it fires, and what data it carries.

Canonical: https://grout.app/developer/documentation/webhooks/events/

Every envelope is `{ id, type, created_at, institution_id, data }`. `GET /v1/events/types` returns this catalogue with a sample payload per type; the portal shows the same under **Developers → Deliveries**.

## People

| Type | When | `data` |
|---|---|---|
| `student.created` | Student provisioned (API, portal or batch) | `user_id, email, user_type, student_id, source` |
| `student.email_mapped` | Personal email linked | `user_id, generated_email, student_id, full_name` |
| `student.updated` | Status or profile changed via API | `user_id, changes[], status?` |
| `faculty.created` | Faculty provisioned | `user_id, email, user_type, faculty_id` |
| `faculty.profile_completed` | Teacher finished first-login profile | `user_id, email, employee_id, department, position` |
| `guardian.invited` | Guardian link created | `student_id, guardian_type, guardian_name` |
| `guardian.verified` | Guardian signed in for the first time | `student_id, guardian_type` |

## Licenses

| Type | When | `data` |
|---|---|---|
| `license.generated` | License issued | `license_id, license_type, user_id, email, expires_at, is_lifetime, max_activations` |
| `license.expired` | Passed expiry | `license_id, user_id, expires_at` |
| `license.device_registered` | New device seat | `user_id, app_id, device_name, platform, app_version, kicked` |

## Exams

| Type | When | `data` |
|---|---|---|
| `exam.published` | Exam visible to students | `exam_id, title, exam_type, app, faculty_id, due_date` |
| `exam.assigned` | Exam assigned to a group | `exam_id, group_id, assigned_by` |
| `exam.attempt_started` | Student opened the exam | `attempt_id, exam_id, student_id, started_at, expires_at` |
| `exam.submitted` | Submission (each version) | `submission_id, exam_id, student_id, is_late, submitted_at, version, app` |
| `exam.graded` | Graded or resubmission requested | `submission_id, exam_id, student_id, score, max_score, percentage, resubmission_required, graded_by` |

## Credits

| Type | When | `data` |
|---|---|---|
| `merits.credited` | Wallet credit from any source | `email, user_id, amount_cents, reason, balance_after_cents` |
| `coupon.batch_requested` | Batch filed for approval | `batch_id, count, value_cents, requested_by` |
| `coupon.batch_approved` | Grout approved it | `batch_id, approved_by` |
| `coupon.redeemed` | Code redeemed by your user | `coupon_id, user_email, value_cents` |

## Alpha Learning

| Type | When | `data` |
|---|---|---|
| `alpha.points_awarded` | Points ledger entry | `student_id, reason, points, ref_type, ref_id, note` |
| `alpha.badge_granted` | Badge earned | `student_id, badge, title` |

## System

| Type | When |
|---|---|
| `ping` | Test event from the portal or `POST /webhooks/endpoints/{id}/test` |

## Wildcards

Subscriptions accept `*`, `<category>.*` (e.g. `exam.*`) or exact types. Events are deduplicated server-side on `(type, ref)`, so re-running an import does not re-fire `student.created` for the same email.
