> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.awardspring.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.awardspring.com/_mcp/server.

# Webhooks

The REST API is how you ask AwardSpring for data. Webhooks are how AwardSpring tells you something changed, without you polling for it.

When an event occurs — an application is submitted, an award is disbursed — AwardSpring POSTs a signed JSON body to a URL you control.

> **Note**
>
> Webhooks are turned on per institution and configured in the AwardSpring admin site, **not** through this API. There is no `/webhooks` endpoint. If you do not see the screen described below, ask your AwardSpring contact to enable webhooks for your institution.

## Setting up an endpoint

In AwardSpring, go to **Settings → API → Webhooks** and add an endpoint. You will need:

| Field                  | Rules                                                                                       |
| ---------------------- | ------------------------------------------------------------------------------------------- |
| **URL**                | Must be `https://`. Plain `http://` is rejected.                                            |
| **Events**             | One or more of the [event types](/webhooks/events). You only receive what you subscribe to. |
| **Notification email** | Optional. Where AwardSpring writes when an endpoint starts failing permanently.             |

An institution can have up to **10** endpoints. Each endpoint gets its own signing secret, shown once on creation and beginning with `whsec_`.

An endpoint can be made inactive at any time. An inactive endpoint receives nothing, and so does every endpoint at an institution whose Webhooks setting has been switched off — the setting gates delivery itself, not just the admin screens.

## The envelope

Every delivery has the same outer shape. Only `data` differs by event type.

```json
{
  "id": "evt_9f0d2b1c3d4e5f60a1b2c3d4e5f60718",
  "type": "application.submitted",
  "occurred_at": "2026-04-03T14:22:31.4820000Z",
  "tenant_id": "T5CHQ7W2N",
  "api_version": "v1",
  "data": {
    "application_id": "A7K2M9QXR",
    "award_cycle_id": "C4T8N2WHP",
    "status": "Submitted",
    "is_auto_submit": false,
    "submitted_at": "2026-04-03T14:22:30.0000000Z",
    "applicant": {
      "user_id": "U9F3K7PZM",
      "first_name": "Ada",
      "last_name": "Nunez",
      "email": "ada.nunez@example.edu",
      "student_id": "STU-44120",
      "external_id": "EXT-44120"
    }
  }
}
```

| Field         | Meaning                                                                                                    |
| ------------- | ---------------------------------------------------------------------------------------------------------- |
| `id`          | Unique event identifier, `evt_` prefixed. **Deduplicate on this.**                                         |
| `type`        | The event type, e.g. `award.created`.                                                                      |
| `occurred_at` | ISO-8601 UTC timestamp of when the event happened, not when it was delivered.                              |
| `tenant_id`   | Your institution's [ID](/ids), the same 9-character form as every other ID. Constant for a given endpoint. |
| `api_version` | Payload contract version. Currently `v1` for every event.                                                  |
| `data`        | Event-specific body. See [Event types](/webhooks/events).                                                  |

A [test send](/webhooks/delivery#test-sends) carries an extra `"is_test": true` alongside `api_version`. Real events never carry it.

Note that `data` uses `snake_case`, matching the REST API. Identifiers inside it are the same 9-character [IDs](/ids) the REST API uses, so an ID from an event can be passed straight back to the matching endpoint. A replay of an event recorded before IDs took this form may still carry whole numbers; [Event types](/webhooks/events#identifiers) explains when.

## Headers on every delivery

```http
POST /your-endpoint HTTP/1.1
Content-Type: application/json
X-AwardSpring-Signature: t=1775227351,v1=5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
X-AwardSpring-Event: application.submitted
X-AwardSpring-Event-Id: evt_9f0d2b1c3d4e5f60a1b2c3d4e5f60718
X-AwardSpring-Delivery-Attempt: 1
```

| Header                           | Use it for                                                                                  |
| -------------------------------- | ------------------------------------------------------------------------------------------- |
| `X-AwardSpring-Signature`        | **Verify this before trusting the body.** See [Verifying signatures](/webhooks/signatures). |
| `X-AwardSpring-Event`            | Routing, without parsing the body. Matches `type`.                                          |
| `X-AwardSpring-Event-Id`         | Deduplication, without parsing the body. Matches `id`.                                      |
| `X-AwardSpring-Delivery-Attempt` | `1` on the first try, incrementing on each retry.                                           |

## What your endpoint must do

1. **Verify the signature.** An unverified endpoint accepts anything anyone posts at it.
2. **Respond `2xx` quickly.** AwardSpring gives up on a delivery after **10 seconds** and treats it as failed. Queue the work; do not do it inline.
3. **Tolerate duplicates.** A retry after a timeout, a manual replay, or a network failure can deliver the same `id` more than once. Treat `id` as an idempotency key on your side.
4. **Tolerate new fields.** Fields get added to `data` without a version change. Ignore what you do not recognise rather than rejecting the payload.

Ordering is **not** guaranteed. Each endpoint's deliveries are independent, and a retried event can arrive after a later one. If order matters, sort on `occurred_at` rather than on arrival.

## Where to go next

* [Verifying signatures](/webhooks/signatures) — the HMAC scheme, and rotating a secret without downtime.
* [Delivery and retries](/webhooks/delivery) — the retry schedule, what happens when an endpoint stays down, and how to replay.
* [Event types](/webhooks/events) — the `data` payload for each event.