Skip to content

Getting Started

This guide walks you from zero to your first push notification in five steps.

Step 1: Register your app

Today, onboarding is done through the admin API (a self-serve dashboard is planned).

You receive:

  • App ID — stable slug for the app (for example my-cool-app)
  • API Key — secret Bearer token used only on your servers
  • VAPID public key — available from GET https://api.gesher.pro/vapid-key as publicKey (or pass it explicitly into the SDK)

Create an app with your admin key:

bash
curl -X POST https://api.gesher.pro/admin/apps \
  -H "Authorization: Bearer YOUR_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "new-app", "name": "New App", "api_key": "YOUR_GENERATED_SECRET_AT_LEAST_32_CHARS"}'

See API Reference — Admin for full create/list/update payloads.

Step 2: Install the client SDK

bash
npm install @gesher/sdk
typescript
import { GesherClient } from '@gesher/sdk'

const gesher = new GesherClient({
  subscribeEndpoint: '/api/push/subscribe', // your backend proxy
  unsubscribeEndpoint: '/api/push/unsubscribe', // your backend proxy
  vapidPublicKey: 'YOUR_VAPID_PUBLIC_KEY', // optional; omit to auto-fetch from Gesher
  serviceWorkerPath: '/gesher-sw.js', // default
})

// Subscribe user
const result = await gesher.subscribe(userId)
if (!result.ok) console.error(result.error)

// Unsubscribe
await gesher.unsubscribe(userId)

// Check state
const state = await gesher.getState()
// → 'subscribed' | 'unsubscribed' | 'denied' | 'unsupported'

The SDK POSTs JSON to your endpoints using camelCase (userId, subscription, endpoint). Your proxy must translate that into the shape Gesher expects (also camelCase — see POST /subscribe).

Step 3: Add the service worker

Copy the template from the package export @gesher/sdk/sw into your app’s static root (for example public/gesher-sw.js):

bash
# Example: copy from node_modules into public/
cp node_modules/@gesher/sdk/gesher-sw.js public/gesher-sw.js

You can instead maintain a custom worker. See the Service Worker guide for behavior, RTL, and deep linking.

Step 4: Create a backend proxy

Why: The Gesher API key must never reach the browser. The SDK only talks to URLs you control; those routes forward to https://api.gesher.pro with Authorization: Bearer <APP_API_KEY>.

Important: Gesher’s subscribe/unsubscribe APIs use camelCase JSON (userId, not user_id). The unsubscribe API on Gesher is DELETE /unsubscribe with a JSON body.

1. Cloudflare Pages Function (subscribe)

typescript
// functions/api/push/subscribe.ts
export const onRequestPost = async ({ request, env }) => {
  // 1. Authenticate YOUR user (Supabase, session cookie, etc.)
  const body = await request.json()
  const { userId, subscription } = body

  // 2. Forward to Gesher (use your app API key from env)
  const res = await fetch('https://api.gesher.pro/subscribe', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${env.GESHER_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      userId,
      subscription,
      tags: body.tags ?? [],
    }),
  })
  return new Response(await res.text(), { status: res.status })
}

2. Supabase Edge Function (Deno)

typescript
Deno.serve(async (req) => {
  const { userId, subscription, tags } = await req.json()
  const res = await fetch('https://api.gesher.pro/subscribe', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${Deno.env.get('GESHER_API_KEY')}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ userId, subscription, tags: tags ?? [] }),
  })
  return new Response(await res.text(), { status: res.status })
})

3. Node.js / Express

typescript
app.post('/api/push/subscribe', async (req, res) => {
  const response = await fetch('https://api.gesher.pro/subscribe', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.GESHER_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      userId: req.body.userId,
      subscription: req.body.subscription,
      tags: req.body.tags ?? [],
    }),
  })
  const text = await response.text()
  res.status(response.status).send(text)
})

For unsubscribe, forward the SDK’s POST body to Gesher with DELETE:

typescript
app.post('/api/push/unsubscribe', async (req, res) => {
  const response = await fetch('https://api.gesher.pro/unsubscribe', {
    method: 'DELETE',
    headers: {
      Authorization: `Bearer ${process.env.GESHER_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      userId: req.body.userId,
      endpoint: req.body.endpoint,
    }),
  })
  const text = await response.text()
  res.status(response.status).send(text)
})

Step 5: Send notifications

From your server:

bash
# To a specific user (all their devices)
curl -X POST https://api.gesher.pro/notify/USER_ID \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "New message!", "body": "You got a reply", "url": "/messages/456"}'

# Broadcast to all subscribers
curl -X POST https://api.gesher.pro/notify \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Maintenance tonight", "body": "We will be down 2-4am"}'

Broadcast returns 202 immediately while delivery continues in the background. See POST /notify.

Next steps

Gesher — managed web push by Otomator