# Gradebook Sync

> Push exam results into your gradebook the moment they are graded.

Canonical: https://grout.app/developer/documentation/guides/gradebook/

## Flow

1. Register a webhook endpoint subscribed to `exam.graded` (and optionally `exam.submitted` for "turned in" states).
2. On `exam.graded`, fetch the submission for the authoritative numbers.
3. Write the score to the gradebook item mapped to `exam_id`.

## Handler sketch

```js
app.post('/hooks/grout', express.raw({ type: 'application/json' }), async (req, res) => {
  if (!verifyGrout(req.body.toString(), req.get('X-Grout-Signature'), SECRET)) return res.status(400).end();
  const evt = JSON.parse(req.body);
  res.status(202).end();                       // ack first
  if (await seen(evt.id)) return;              // dedupe on event id
  if (evt.type !== 'exam.graded') return;

  const { data } = await grout(`/submissions/${evt.data.submission_id}`);
  const item = await gradeItemFor(evt.data.exam_id);
  if (evt.data.resubmission_required) await markNeedsRevision(item, data.student_id, data.feedback);
  else await postGrade(item, data.student_id, data.score, data.max_score, data.feedback);
});
```

## Mapping exams to items

Create the gradebook item when you see `exam.published` (title, `max_score`, `due_date`), store `exam_id → item_id`, and assign the exam to the section's group. That keeps authoring in Grout and reporting in the LMS.

## Backfill

Missed events? Pull them:

```bash
curl "$API/events?type=exam.graded&since=2026-09-01T00:00:00Z" -H "Authorization: Bearer $KEY"
curl "$API/submissions?exam_id=ex_…&status=graded" -H "Authorization: Bearer $KEY"
```

Or export a whole exam with `GET /exams/{id}/export.csv`.
