API

Base URL https://push.dreamsengine.io. All resources are scoped to a tenant slug: /v1/:tenant/…. JSON in, JSON out.

Authentication

Three access classes:

Public (origin-checked)

Subscribe, unsubscribe, vapid-public, click events. No key — browser-called, gated by the tenant's allowed-origins allowlist via CORS.

HMAC (publish)

X-DPE-Signature = HMAC-SHA256(rawBody, tenantSecret), hex. Plus a required Idempotency-Key header.

Admin

Bearer ADMIN_TOKEN, or Cloudflare Access identity. Guards tenant provisioning and stats.

Store a subscription

POST/v1/:tenant/subscriptionsorigin
POST https://push.dreamsengine.io/v1/cobertura/subscriptions
Content-Type: application/json

{
  "endpoint": "https://fcm.googleapis.com/fcm/send/…",
  "keys": { "p256dh": "…", "auth": "…" },
  "categories": ["ultima-hora"]
}
-> 201 { "ok": true }

Remove a subscription

DELETE/v1/:tenant/subscriptionsorigin
DELETE https://push.dreamsengine.io/v1/cobertura/subscriptions
{ "endpoint": "https://fcm.googleapis.com/fcm/send/…" }
-> 200 { "ok": true }

Get the VAPID public key

GET/v1/:tenant/vapid-publicpublic
GET https://push.dreamsengine.io/v1/cobertura/vapid-public
-> 200 { "publicKey": "BEO7Xhre…" }

The frontend passes this to PushManager.subscribe() as the applicationServerKey.

Publish (fan-out webhook)

POST/v1/:tenant/publishHMAC
POST https://push.dreamsengine.io/v1/cobertura/publish
Idempotency-Key: <post-guid>:<unix-ts>     # required, dedups retries
X-DPE-Signature: <hmac-sha256(rawBody, secret) hex>
Content-Type: application/json

{
  "title": "string (truncated to 64)",
  "body":  "string (truncated to 120)",
  "url":   "https://… (click target)",
  "icon":  "https://… (optional)",
  "image": "https://… (optional)",
  "categories": ["ultima-hora"],
  "send": true
}
-> 202 { "delivery_id": "…" }          # enqueued
-> 200 { "delivery_id": "…", "replay": true }   # idempotent replay

Fan-out is asynchronous. Subscriptions with no categories receive everything; otherwise they must overlap the publish categories.

Record a click

POST/v1/:tenant/events/clickorigin
POST https://push.dreamsengine.io/v1/cobertura/events/click
{ "delivery_id": "…" }     # posted by the service worker on notificationclick
-> 200 { "ok": true }

Tenant stats

GET/v1/:tenant/statsadmin
GET https://push.dreamsengine.io/v1/cobertura/stats   (Authorization: Bearer …)
-> {
  "subscribers": 1240,
  "byCategory": [ { "category": "ultima-hora", "count": 980 } ],
  "deliveries": { "total": 42, "sent": 41200, "failed": 90, "pruned": 310, "clicked": 5100 },
  "ctr": 0.123,
  "recent": [ … ]
}

Provision a tenant

POST/v1/tenantsadmin
POST https://push.dreamsengine.io/v1/tenants   (Authorization: Bearer …)
{ "slug": "cobertura", "name": "Cobertura 360",
  "allowedOrigins": ["https://cobertura360.mx"] }
-> 201 {
  "slug": "cobertura",
  "vapid_public": "BEO7…",
  "api_secret": "…"      # shown ONCE — store it now
}

Frontend integration flow

The property frontend (not this service) owns the browser side:

Ship a service worker + PWA manifest

The SW handles push and notificationclick. A manifest + icons are required for iOS web push (Add to Home Screen).

Soft opt-in

Show a custom CTA and call Notification.requestPermission() only on positive intent — never on load.

Subscribe

Fetch the VAPID public key, call PushManager.subscribe(), then POST the result to the subscribe endpoint.

Report clicks

In the SW notificationclick handler, POST the deliveryId from the payload to the click endpoint to drive CTR.