# Embeds & webhooks (Pro)

Both halves of the ERP/IMS story: the **embed** puts the test player inside
your site, the **webhook** pushes results back to your server. No polling,
no iframes-of-dashboards.

## Embedding a test

```html
<script src="https://siddhixsys.com/embed-test.js" data-test="TESTCODE" async></script>
```

- Replace `TESTCODE` with the code from the test's share panel.
- The player renders where the tag sits and **auto-resizes** its height.
- Optional `data-min-height="600"`.
- Students sign in inside the frame exactly as they would on the direct link —
  personal links (`?c=` codes) also work embedded.
- Requires the organizer to have an **active Pro plan**; otherwise the frame
  shows an "open in full window" card.
- Heavy-proctored tests always open in their own window (fullscreen and camera
  cannot be enforced inside an iframe — we refuse rather than pretend).

## Webhooks

Add endpoints in **Agents & API → Webhooks**. We POST JSON:

- `attempt.submitted` — a student finished (or time expired). `attempt.state`
  is `evaluated` (final score) or `submitted` (written answers pending).
- `result.published` — you published results; the payload carries every
  published row: `{name, email, phone, score, max_score}`.
- `ping` — the "Send test ping" button.

```json
{
  "event": "attempt.submitted",
  "created_at": "2026-08-04T12:00:00.000Z",
  "test": { "id": 12, "code": "aB3xY9zQ", "title": "Weekly test 4" },
  "taker": { "name": "Asha", "email": "asha@example.com", "phone": null, "source": "roster" },
  "attempt": { "id": 88, "state": "evaluated", "score": 42, "max_score": 60,
               "submitted_at": "2026-08-04T11:59:31.000Z", "auto_submitted": false }
}
```

### Verifying the signature

Every delivery carries `X-Amatya-Signature: t=<unix seconds>,v1=<hex>` where
`v1 = HMAC-SHA256(secret, `${t}.${rawBody}`)`:

```js
const crypto = require('node:crypto');
function verify(header, rawBody, secret) {
  const { t, v1 } = Object.fromEntries(header.split(',').map(p => p.split('=')));
  const expect = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300; // 5-minute window
  return fresh && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expect));
}
```

### Delivery rules

- Respond **2xx within 10 seconds**; anything else counts as a failure.
- Retries: 6 attempts with growing backoff (1m → 5m → 30m → 2h → 12h).
- After 12 consecutively failed deliveries the webhook pauses itself —
  resume it from the panel once your endpoint is fixed.
- Endpoints must be **public https** — private/internal addresses are rejected
  at creation AND re-checked at every delivery.
- Redirects are not followed.

The same webhook fires for every test you run — filter on `test.id` or
`test.code` in your handler.
