Off-Nadir Delta

Get notified in your own system when a watched place changes

You follow a facility, an area or an event and want each change pushed into your own tooling — a ticket, a dashboard, an alert channel — instead of someone checking a screen or a script polling for it.

Steps

  1. Watch the place: a site (a registry facility or any point, at facility, city or country scale), an area or an event. Watches are free.
  2. Register your https endpoint as the notification channel and keep the signing secret — it is returned only once.
  3. Turn on notify_chat for the watches you want delivered, then send a test delivery to check the receiver.
  4. In the receiver, verify X-Delta-Signature over the raw body and reject an old timestamp. Each change says what moved, with its value before and after where your plan opens it.
  5. Deliveries run with the hourly notifications. If the receiver was down, ask for a replay (up to 7 days per call): every change carries its ledger id, so one you already have is recognised.

In the web app

Open your Watchlist →

REST

Authenticate with Authorization: Bearer ond_… (create a key at /account/api). Try any of these live in the Playground.

Watch a site (free)

bash
curl "https://offnadir-delta.com/api/v1/watches" \
  -X POST \
  -H "Authorization: Bearer ond_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"target_type":"site","lat":46.49,"lon":30.74,"scale":"facility","name":"Odesa port"}'

Register the webhook (free; the signing secret is returned once)

bash
curl "https://offnadir-delta.com/api/v1/notification-channel/webhook" \
  -X POST \
  -H "Authorization: Bearer ond_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/delta-hook"}'

Send a test delivery (free)

bash
curl "https://offnadir-delta.com/api/v1/notification-channel/test" \
  -X POST \
  -H "Authorization: Bearer ond_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Replay what the receiver missed (free)

bash
curl "https://offnadir-delta.com/api/v1/notification-channel/replay" \
  -X POST \
  -H "Authorization: Bearer ond_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"since":"2026-10-18T00:00:00Z"}'

Python

This is the runnable example shipped with the SDK (pip install offnadir-delta, then set OFFNADIR_DELTA_API_KEY).

python
"""Receive watch notifications on your own endpoint and verify each delivery.

1. Register the endpoint once (it must be https on port 443 and reachable from the internet —
   run this behind your reverse proxy or a tunnel):

       from offnadir_delta import Client
       with Client() as client:
           channel = client.notifications.channel.connect_webhook("https://example.com/delta-hook")
           print(channel.signing_secret)   # returned only once — store it

2. Turn on notifications for the watches you want delivered (the watch's notify_chat setting,
   in the app or with client.watches.update).

3. Run this receiver:  DELTA_WEBHOOK_SECRET=whsec_... python examples/webhook_receiver.py

Deliveries run with the hourly notifications. Each change has a ledger id, so a change sent
again by notifications.channel.replay() is recognised and skipped.
"""

import os
from http.server import BaseHTTPRequestHandler, HTTPServer

from offnadir_delta import WebhookSignatureError, verify_webhook

SECRET = os.environ["DELTA_WEBHOOK_SECRET"]
PORT = int(os.environ.get("PORT", "8080"))

# Ids of changes already handled. Keep these in your database in a real receiver.
seen: set = set()


class DeltaWebhook(BaseHTTPRequestHandler):
    def do_POST(self) -> None:
        raw = self.rfile.read(int(self.headers.get("Content-Length", "0")))
        try:
            # The raw bytes, exactly as received — parsing and re-serialising would change them.
            event = verify_webhook(raw, self.headers.get("X-Delta-Signature"), SECRET)
        except WebhookSignatureError as exc:
            self.send_response(400)
            self.end_headers()
            print(f"rejected: {exc}")
            return

        if event.get("type") == "test":
            print("connected:", event.get("message"))
        else:
            watch = event["watch"]["name"]
            for change in event["changes"]:
                if change["id"] in seen:
                    continue  # already handled (for example, sent again by a replay)
                seen.add(change["id"])
                # `line` is always present; label / from / to are null when your plan does not open them.
                print(f"[{watch}] {change['line']}")

        # Answer 2xx quickly. A 404 or 410 stops the channel; other errors are retried each hour.
        self.send_response(204)
        self.end_headers()


if __name__ == "__main__":
    print(f"listening on :{PORT}")
    HTTPServer(("", PORT), DeltaWebhook).serve_forever()

MCP

Connect once, from any MCP client:

bash
claude mcp add --transport http off-nadir-delta \
  https://offnadir-delta.com/api/v1/mcp \
  --header "Authorization: Bearer ond_..."

The tools this workflow uses: create_watch, update_watch, list_notifications. See the full roster at /docs/mcp.

Go deeper

← All recipes

From headline to satellite evidence

One connected intelligence workflow across four surfaces — free to start, no GIS software or remote-sensing background required.