Creator Notify v1.0.0

Troubleshooting

Symptom first, cause second, fix third. Every entry corresponds to behaviour in the code.

"Permission denied" or no prompt appears

  • The prompt never shows: Notification.requestPermission() was called outside a user gesture, or the page is not a secure context. Move the call into a click handler; check window.isSecureContext.
  • getPermission() returns 'denied': the user (or a browser policy) blocked notifications for the origin. It can only be re-enabled in browser settings (Chrome: padlock → Site settings → Notifications).
  • iOS Safari: push requires the site to be added to the Home Screen (iOS 16.4+) and permission granted from the installed web app.
  • Firefox private window: push subscriptions are typically unavailable.

Service Worker registration errors

ServiceWorkerRegistration failed: The script has an unsupported MIME type ('text/html')

Your web server returned an HTML 404 page for the worker path. Serve the real file with a JavaScript MIME type.

Failed to register a ServiceWorker: The origin of the provided scriptURL
('https://cdn.example.net/sw.js') does not match the current origin ('https://example.com')

The worker must be on your own origin. Copy it there; do not load it from a CDN.

The Service Worker navigation preload ... / scope

A worker at /js/service-worker.js can only control /js/*. Move it to the root or send Service-Worker-Allowed: / and register with serviceWorkerScope: '/'.

HTTPS requirements

Service Workers, Push and Notifications require a secure context: an HTTPS origin, or http://localhost / 127.0.0.1 during development. A plain-HTTP staging host will fail isSupported(). Mixed content (an HTTPS page loading the SDK over HTTP) is blocked by the browser.

Invalid VAPID keys

SymptomCauseFix
503 SERVICE_UNAVAILABLE — "VAPID configuration is invalid"Missing or malformed VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECTRe-run node scripts/generate-vapid-keys.mjs and set all three
ApplicationServerKey is not valid in the browserThe public key was pasted with whitespace, wrong padding, or is not 65 bytesUse the base64url value exactly as generated
Publish fails with HTTP_401/HTTP_403 from the push serviceVAPID key mismatch with existing subscriptions, or an invalid sub claimKeep one key pair per deployment; VAPID_SUBJECT must be mailto: or https:
Subscriptions created before a key change stop workingSubscriptions are bound to the application server keySubscribers must re-subscribe; do not rotate VAPID keys casually

Invalid push subscriptions

INVALID_SUBSCRIPTION (400) means the payload was malformed: the endpoint is not HTTPS, the host is not allow-listed, p256dh is not a 65-byte point, or auth is not 16 bytes. The SDK produces these automatically, so a failure here usually means something else is posting to the API.

PUSH_ENDPOINT_NOT_ALLOWED means the endpoint host is not in ALLOWED_PUSH_HOSTS. Add the host (for example a new vendor push service) and redeploy — or, for a private deployment, list the hosts you trust explicitly.

When a push service itself returns 404/410 during delivery, the subscription is marked invalid and excluded from future sends. That is expected churn, not an error you can fix.

CORS errors

Access to fetch at 'https://YOUR-API-HOST/v1/subscriptions' from origin 'https://example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.
  • The origin is not registered, or does not match exactly (scheme, host and port all count). Registering https://example.com does not authorize https://www.example.com.
  • You are calling a management endpoint (/v1/notifications, /v1/sites/status) from a browser. Those routes deliberately send no CORS headers — sending is server-to-server only.
  • The Origin header is missing: a file:// page, a sandboxed iframe, or a cross-origin no-cors request. Serve the page over HTTP(S).
  • You set credentials: 'include' in fetchOptions; this API never uses cookies and does not allow credentials.

Expired registrations

{ "success": false, "error": { "code": "SITE_EXPIRED", ... } }

No successful publish for INACTIVITY_DAYS (7 by default). Call POST /v1/sites/reactivate with the same key, then publish. Subscription and notification data are untouched. See Lifecycle.

For a registration that expires repeatedly, the cause is almost always a cron/job that was never scheduled, or a publish job that fails silently. Alert on SITE_EXPIRED in your sender.

Failed push delivery

Error code in delivery attemptsMeaningAction
HTTP_404, HTTP_410Endpoint gone — recorded as invalidNone; the subscription is retired
HTTP_401, HTTP_403VAPID signature rejectedCheck the key pair and VAPID_SUBJECT
HTTP_413Payload too large for that push serviceShorten body/data; lower MAX_PUSH_PAYLOAD_BYTES
HTTP_429Push service throttlingReduce concurrency, retry later — recorded as failed, subscription kept
HTTP_5xxPush service outageRetry; the subscription is kept
TIMEOUTNo response within DELIVERY_TIMEOUT_MSRaise the timeout or lower concurrency
NETWORK_ERRORDNS/TLS failure reaching the endpointUsually transient
ENCRYPTION_FAILEDStored key material cannot encryptSubscriber must re-subscribe
AUTH_DECRYPT_FAILEDThe encrypted auth secret no longer decrypts (key changed)Subscriber must re-subscribe

A 201 from the push service means "accepted", not "shown". If notifications are accepted but never appear, check: OS-level notification settings for the browser, site-level mute, Do Not Disturb, browser "quiet mode", and whether the user is on a browser whose push service silently drops the endpoint.

Documentation returns 404

The docs are generated at build time. Run npm run build:assets and redeploy; a fresh clone that was never built (or a deploy that skipped the prepare step) will not have src/generated/assets.js.

Database and state errors

SymptomMeaning
no such table: sitesThe instance you are calling has no schema applied. This is an operator problem — self-hosters apply the migrations in the repository.
DATABASE_ERROR: too many SQL variablesA delivery batch was built larger than the database allows; the operator should lower DELIVERY_PAGE_SIZE.
Publishes stay processingThe request was cut short and the background sweep has not picked it up yet. The sweep runs every 15 minutes and resumes mid-flight notifications.
503 with "database": "unavailable"From /health: the instance cannot reach its database. Nothing your integration can fix — retry with backoff.

Useful commands

# what to send the operator (or your own log search)
request id: x-request-id response header
endpoint:   the path you called
time:       UTC timestamp of the failure
outcome:    error code + message, verbatim