# geo.record

> Sent the minute a record that one of your Geo alert rules matches is written.

Source: https://firmtape.com/docs/developers/geo/events/geo.record

Sent the minute a record that one of your Geo alert rules matches is written.

`POST your https address` · `event: geo.record`

Plan GEO · Payload unversioned · Signed HMAC-SHA256 · Schema geo.record.schema.json

**This event serves:** [GEO: Geo alerts](https://firmtape.com/docs/geo#geo-alerts), [GEO: The API, webhooks and MCP](https://firmtape.com/docs/geo#geo-api). **Plan:** [GEO](https://firmtape.com/features/geo). Free to read, no account. Geo alerts, the API and Team are Geo's own plans: Geo plans. Turn it on: Account, Geo, Webhooks: an endpoint; Account, Geo, Alerts: a rule with Webhooks ticked.

## When it is sent

- To every endpoint of the account, for every record a rule with Webhooks ticked matches.

## Payload

| Field | Meaning | Unit |
| --- | --- | --- |
| `event` ("geo.record") | `geo.record`. |  |
| `sent_utc` (string) | When it was sent, ISO 8601 UTC. |  |
| `record` (object) | The record, with the fields of `GET /v1/items`. |  |

## Delivery

- **Answer**: Any 2xx within 10 seconds. Redirects are not followed.
- **Retries**: One attempt; a failed delivery can be retried by hand from the account page.
- **Switched off**: Not switched off.
- **Volume**: Up to 600 records an hour per account.
- **Test**: `event: "geo.test"`, from **Test** beside the endpoint
- **Log**: Every delivery's status and time on the account page, with a retry button.

## Request we send

```
POST https://your.server/firmtape
Content-Type: application/json
X-FirmTape-Webhook-Id: evt_3f9a0c6e21d4b7a8e5f10c2d
X-FirmTape-Signature: t=1760034725,v1=<hex>
```

## Body

```json
{
  "event": "geo.record",
  "sent_utc": "2026-09-17T13:02:12Z",
  "record": {
    "uid": "8f1c2a90d4b7e613",
    "feed": "faa_tfr",
    "sector": "air",
    "severity": 2,
    "title": "Temporary flight restriction",
    "url": "https://example.org/notice"
  }
}
```

## Verify

```js
const crypto = require("node:crypto");
function verifyGeo(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(String(header).split(",").map((p) => p.split("=")));
  if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const mac = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return Boolean(v1) && mac.length === v1.length && crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(v1));
}
```
