Skip to content

Webhook notifications

The notifier service watches the canonical database and delivers webhook events when prices move or models change lifecycle state. It lives in the repository under services/notifier and stores its state in SQLite (default ~/.tokenpricing/notifier.db).

Run the service

bash
cd services/notifier
uv sync --group dev
uv run notifier serve --host 127.0.0.1 --port 8000

Two companion commands handle polling:

bash
uv run notifier sync --deliver    # one polling cycle, then flush pending deliveries
uv run notifier worker            # long-running poller (default interval: 6 hours)

Create a subscription

bash
curl -X POST http://127.0.0.1:8000/subscriptions \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": "https://example.com/hooks/tokenpricing",
    "description": "Anthropic price moves",
    "filters": {
      "provider": "anthropic",
      "event_types": ["pricing_changed", "cache_price_changed", "model_deprecated", "model_removed"]
    }
  }'

The response includes the subscription id and a secret used to sign deliveries — store it.

Filters

All filter fields are optional; omitted fields match everything.

FieldExampleNotes
model_idanthropic/claude-opus-4Exact model
provideranthropicProvider slug
model_familyclaudeFamily grouping
model_typetextOpenRouter taxonomy: text, image, embeddings, audio, video, rerank, speech, transcription
categoryflagshipbudget, standard, flagship
supports_visiontrueCapability flags
supports_function_callingtrue
event_typessee belowDefaults to pricing, cache, deprecation, and removal events

Event types

pricing_changed · pricing_increased · pricing_decreased · cache_price_changed · model_added · model_deprecated · model_removed

API endpoints

MethodPathPurpose
GET/healthLiveness check
GET/subscriptionsList subscriptions
POST/subscriptionsCreate a subscription
GET/subscriptions/{id}Fetch one
PATCH/subscriptions/{id}Update URL, status, or filters
DELETE/subscriptions/{id}Delete
POST/subscriptions/{id}/testSend a test delivery
POST/subscriptions/{id}/verifyVerify reachability
POST/subscriptions/{id}/rotate-secretRotate the signing secret
POST/syncTrigger one polling cycle
POST/deliveries/flushFlush pending deliveries
GET/eventsRecent detected events
GET/deliveriesRecent delivery attempts

Verify deliveries on your endpoint

Every delivery is HMAC-SHA256 signed. Headers:

HeaderContent
X-Tokenpricing-DeliveryDelivery id
X-Tokenpricing-EventEvent type
X-Tokenpricing-TimestampISO timestamp used in the signature
X-Tokenpricing-Signaturesha256=<hexdigest>

The signature covers "{timestamp}." + body, keyed with the subscription secret:

python
import hashlib, hmac

def is_valid(secret: str, timestamp: str, body: bytes, signature: str) -> bool:
    message = f"{timestamp}.".encode() + body
    expected = "sha256=" + hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

Delivery guarantees

Failed deliveries are retried with backoff (immediately, then 1 m, 5 m, 30 m, 2 h) up to 5 attempts before being dead-lettered. A 2xx response from your endpoint marks the delivery as sent.

Pricing data synchronized from OpenRouter and LiteLLM every six hours.