Documentation

Pinglet SDK Documentation

Everything you need to integrate real-time notifications into your website. From quick start to advanced configuration.

Overview

Pinglet is a real-time notification platform that lets you send push notifications, glassmorphism card notifications, and custom template notifications to your users with a single API call. In v0.0.3, all in-app types use a unified glassmorphism renderer. The SDK connects via SSE (Server-Sent Events) for instant delivery.

4 Notification Types

Browser push, glassmorphism card (Type 0 & 2), and custom templates

Real-time via SSE

Instant delivery through Server-Sent Events — no polling needed

Fully Customizable

Override position, theme, sounds, branding, progress bars, and more

Custom Events

Trigger frontend JS events from notification button clicks

Quick Start

Get notifications running on your site in under 2 minutes.

1

Create a Project

Sign up at Pinglet, create a project, and register your website domain. You'll get a project_id and pinglet_id.

2

Add Script Tags

Add the Pinglet SDK to your HTML, before the closing </body> tag.

html<!-- Service Worker (handles browser push) -->
<script
  crossorigin="anonymous"
  src="https://pinglet.enjoys.in/api/v1/public/scripts/v0.0.3/sw.js"
></script>

<!-- Main SDK -->
<script
  type="module"
  crossorigin="anonymous"
  src="https://pinglet.enjoys.in/api/v1/public/libs/pinglet-sse.js"
  data-endpoint="https://pinglet.enjoys.in/api/v1/notifications"
  data-configured-domain="yourdomain.com"
  data-project-id="YOUR_PROJECT_ID"
  data-pinglet-id="YOUR_PINGLET_ID"
  data-checksum="sha384-XXXXXXX"
></script>
3

Send a Notification

Make a POST request to send your first notification.

bashcurl -X POST https://pinglet.enjoys.in/api/v1/notifications/send \
  -H "Content-Type: application/json" \
  -H "X-Project-ID: YOUR_PROJECT_ID" \
  -d '{
    "project_id": "YOUR_PROJECT_ID",
    "type": 0,
    "variant": "default",
    "body": {
      "title": "Hello from Pinglet!",
      "description": "Your first notification"
    }
  }'

Script Tag Attributes

Reference for all data-* attributes on the SDK script tag.

AttributeRequiredDescription
data-endpointYesPinglet API URL + /notifications
data-configured-domainYesDomain registered in dashboard
data-project-idYes24-char project ID
data-pinglet-idYesPinglet signature token
data-checksumYesSRI checksum (sha384)
data-testimonialsNo"true" to show floating testimonials button
data-templatesNoComma-separated template IDs to preload

Notification Types

Pinglet supports 4 notification types. Each type uses the same API endpoint but with different payloads.

TYPE 0Live

Glassmorphism

Frosted-glass card with rich media, stacking, tag dedup, and dark mode. Primary renderer in v0.0.3.

TYPE 2Live

Glassmorphism (Compat)

Backward compatible alias — maps to Type 0. Both render identically.

TYPE 1Upcoming

Custom Template

Server-stored HTML/CSS templates with variable injection

TYPE -1Upcoming

Browser Push

Native OS push notifications via Web Push API & service worker

TYPE 0Live

Glassmorphism Notification

Modern frosted-glass card notification with stacking queue, 4-corner positioning, tag dedup, rich media, action buttons, branding footer, progress bar, and dark mode. This is the primary in-app renderer in SDK v0.0.3.

jsonPOST /api/v1/notifications/send

{
  "projectId": "your-project-id",
  "type": "0",
  "body": {
    "title": "New Message",
    "description": "You have 3 unread messages",
    "icon": "🔔",
    "logo": "https://cdn.example.com/logo.png",
    "url": "https://example.com/inbox",
    "media": { "type": "image", "src": "https://cdn.example.com/banner.jpg" },
    "buttons": [
      { "text": "View", "action": "redirect", "src": "https://..." },
      { "text": "Dismiss", "action": "close" }
    ]
  }
}

Body Fields

FieldRequiredNotes
titleYesMin 3 chars
descriptionNoBody text
iconNoURL, emoji, SVG, or base64
logoNoURL to an image. Used as fallback if no icon.
urlNoClick-through URL (must be valid)
mediaNo{type, src} — type: image|video|audio|iframe
buttonsNoArray, max 3 buttons

What Happens

  1. SSE delivers to all connected clients for this projectId
  2. SDK calls showHtmlNotification({...}) — unified glassmorphism renderer
  3. Card rendered with glassmorphism CSS, icon prefetched
  4. If > maxVisible (default 3), notification is queued
  5. Auto-dismisses after config.duration (default 5000ms)

Examples

json// Minimal
{ "projectId": "...", "type": "0", "body": { "title": "Hello!" } }

// With icon + description
{
  "projectId": "...", "type": "0",
  "body": { "title": "Order #123", "description": "Ready for pickup", "icon": "📦" }
}

// With image media
{
  "projectId": "...", "type": "0",
  "body": { "title": "Sale!", "media": { "type": "image", "src": "https://..." } }
}
TYPE 2Live

Glassmorphism (Backward Compatible)

Type 2 is a backward compatible alias — in v0.0.3, it renders identically to Type 0 via showHtmlNotification(). Use Type 0 for new integrations. Existing Type 2 payloads will continue to work.

jsonPOST /api/v1/notifications/send

{
  "projectId": "your-project-id",
  "type": "2",
  "body": {
    "title": "Flash Sale 🔥",
    "description": "50% off all items.",
    "icon": "https://cdn.example.com/icon.png",
    "url": "https://example.com/sale",
    "media": { "type": "image", "src": "https://cdn.example.com/hero.jpg" },
    "buttons": [
      { "text": "Shop", "action": "redirect", "src": "https://..." },
      { "text": "Track", "action": "event", "event": "sale:click", "data": {} },
      { "text": "Close", "action": "close" }
    ]
  }
}

Body Fields

FieldRequiredNotes
titleYesMin 3 chars
descriptionNoBody text
iconNoURL allowed for type 2 (unlike type 0)
urlNoClick-through URL
mediaNo{type, src} — image, video, audio, or iframe
buttonsNoArray, max 3 buttons

v0.0.3 — Type 2 = Type 0

  • Both types use the same showHtmlNotification() renderer
  • Type 2 exists for backward compatibility — no functional difference
  • Same body schema, same overrides, same rendering
  • Use Type 0 for all new integrations
  • Old toast/variant system was removed in v0.0.3

What Happens

  1. SSE delivers the payload
  2. SDK calls showHtmlNotification({...})
  3. Card rendered with glassmorphism CSS
  4. If > maxVisible (default 3), queued and shown when a slot opens
  5. Branding footer from globalConfig.config.branding
  6. Auto-closes with progress bar (unless requireInteraction)

Examples

json// Minimal
{ "projectId": "...", "type": "2", "body": { "title": "Welcome!" } }

// Full
{
  "projectId": "...", "type": "2",
  "body": {
    "title": "Payment Received",
    "description": "$99.00 from [email protected]",
    "icon": "https://cdn.example.com/avatar.png",
    "buttons": [
      { "text": "View", "action": "redirect", "src": "https://..." },
      { "text": "Close", "action": "close" }
    ]
  }
}
TYPE -1Upcoming

Browser Push Notification

Notifications delivered via Web Push API → Service Worker → native OS notification. Reaches users even when the tab is closed. Requires user's push permission grant.

Important: Uses data field (NOT body). Follows browser Notification API spec. Max 2 action buttons (browser limitation). Queued via BullMQ on backend (not SSE).

jsonPOST /api/v1/notifications/send

{
  "projectId": "your-project-id",
  "type": "-1",
  "data": {
    "title": "Order Shipped",
    "body": "Your package is on the way.",
    "icon": "https://cdn.example.com/icon.png",
    "badge": "https://cdn.example.com/badge.png",
    "image": "https://cdn.example.com/hero.jpg",
    "tag": "order-123",
    "requireInteraction": true,
    "silent": false,
    "renotify": false,
    "dir": "ltr",
    "timestamp": 1711000000000,
    "vibrate": [200, 100, 200],
    "data": {
      "url": "https://example.com/track/123",
      "duration": 5000
    },
    "actions": [
      { "action": "view", "title": "View Order", "icon": "..." },
      { "action": "dismiss", "title": "Dismiss" }
    ]
  }
}

Data Fields

FieldRequiredNotes
titleYes1-100 chars
bodyNoMax 500 chars
iconNoURL — notification icon
badgeNoURL — small overlay (Android)
imageNoURL — large hero image
tagNoDedup — same tag replaces previous
requireInteractionNotrue = stays until user clicks
silentNotrue = no sound/vibration
renotifyNotrue = re-alert even if same tag
dirNo"auto" | "ltr" | "rtl"
timestampNoUnix ms — shown as time
vibrateNoArray of vibration durations
data.urlNoURL opened on click
actionsNoMax 2 action buttons (browser limit)

Prerequisite

The browser must have push subscription registered. The SDK does this automatically via askNotificationPermission() on load. If the user denied permission, push won't work.

TYPE 1Upcoming

Custom Template Notification

Server-stored templates created in the dashboard. Pass data to fill template placeholders. Useful for consistent, reusable notification formats.

Important: Requires template_id AND custom_template. Must NOT include body or data. Template must be pre-loaded (via data-templates="1,42" or loadAllTemplates).

jsonPOST /api/v1/notifications/send

{
  "projectId": "your-project-id",
  "type": "1",
  "template_id": "42",
  "custom_template": {
    "user_name": "John Doe",
    "order_total": "$49.99",
    "action_url": "https://example.com/orders/123",
    "product_image": "https://cdn.example.com/product.jpg"
  }
}

Rules

  • template_id must be a string matching an existing template
  • custom_template is a key-value object — any shape, depends on template
  • body must NOT be present when type is "1"
  • data must NOT be present when type is "1"
  • variant must NOT be present when type is "1"

What Happens

  1. SSE delivers { type: "1", template_id, custom_template }
  2. SDK looks up template from globalConfig.templates[template_id]
  3. Executes compiled_text as a function(data, config, globalConfig)
  4. Result passed as customContent — wrapped in glassmorphism container with stacking, close button, and progress bar

API Reference

Send notifications via the Pinglet REST API. Rate limit: 30 requests/minute.

POSThttps://pinglet.enjoys.in/api/v1/notifications/send

Headers

json{
  "Content-Type": "application/json",
  "X-Project-ID": "your-project-id",
  "X-Pinglet-Version": "1.0.5"
}

Payload Schemas

Request body schemas for each notification type.

Glassmorphism

Glassmorphism frosted-glass card notification. Stacking queue, 4-corner positioning, tag dedup, rich media, and dark mode. The primary in-app renderer in v0.0.3.

POSThttps://pinglet.enjoys.in/api/v1/notifications/send

Headers

json{
  "x-project-id": "your-project-id",
  "x-pinglet-version": "1.0.5"
}

Request Body

json{
  "project_id": "your-project-id",
  "type": 0,
  "variant": "default",
  "body": {
    "title": "Hello from Pinglet",
    "description": "New design incoming!",
    "media": {
      "type": "icon",
      "src": "🔥"
    },
    "buttons": [
      {
        "text": "Fix Now",
        "action": "link",
        "src": "https://example.com/action"
      },
      {
        "text": "Dismiss",
        "action": "close"
      }
    ]
  },
  "overrides": {
    "auto_dismiss": false
  }
}

Response

json{
  "message": "OK",
  "result": "Notification Sent",
  "success": true,
  "X-API-PLATFORM STATUS": "OK"
}

Request Schema

project_idstringrequired

Your project unique ID

typenumberrequired

Set to 0 for in-tab

variantstring

"default"

body.titlestringrequired

Notification title

body.descriptionstring

Notification body

body.iconstring

Emoji / text / SVG / base64

body.logostring

URL or base64

body.urlstring

Open URL on click

body.media.typestring

"image" | "audio" | "video" | "iframe"

body.media.srcstring

Must be a valid URL

body.buttons[]array

Action buttons

body.buttons[].textstringrequired

Button label

body.buttons[].actionstringrequired

"reload" | "close" | "redirect" | "link" | "alert" | "event"

body.buttons[].srcstring

URL or message for redirect/link/alert

body.buttons[].eventstring

Event name (when action="event")

body.buttons[].dataany

Custom event data

overrides.positionstring

"top-right" | "top-left" | "bottom-right" | "bottom-left"

overrides.transitionstring

"fade" | "slide" | "zoom"

overrides.durationnumber

Auto dismiss after ms

overrides.auto_dismissboolean

Enable auto dismiss

overrides.maxVisiblenumber

Max visible notifications

overrides.stackingboolean

Stack multiple notifications

overrides.dismissibleboolean

Allow manual dismiss

overrides.sound.playboolean

Play notification sound

overrides.sound.srcstring

Sound URL

overrides.sound.volumenumber

0 to 1

overrides.branding.showboolean

Show branding

overrides.branding.onceboolean

Show only once

overrides.branding.htmlstring

Custom branding HTML

overrides.theme.modestring

"light" | "dark" | "auto"

overrides.theme.customClassstring

Custom CSS class

overrides.theme.roundedboolean

Rounded corners

overrides.theme.shadowboolean

Drop shadow

overrides.theme.borderboolean

Show border

overrides.progressBar.showboolean

Show progress bar

overrides.progressBar.colorstring

Hex/RGB color

overrides.progressBar.heightnumber

Height in px

overrides.iconDefaults.showboolean

Show icon

overrides.iconDefaults.sizenumber

Icon size in px

overrides.iconDefaults.positionstring

"left" | "right" | "top"

overrides.websitestring

Target website URL

overrides.timeboolean

Show timestamp

overrides.faviconboolean

Show favicon

Custom Events

Trigger real DOM CustomEvents on the user's browser when they click a notification button. Your frontend JavaScript listens for the event and executes any logic — add to cart, open modal, track conversion, and more.

How It Works

1. Backend sends notification with button → action: "event"
2. SDK renders notification → User clicks button
3. SDK fires: window.dispatchEvent(new CustomEvent("name", {detail: payload}))
4. SDK auto-dismisses notification + tracks click
5. Your JS listener catches the event and runs your logic

Event Button Schema

textstringrequired

Button label shown to the user

action"event"required

Must be exactly "event"

eventstringrequired

Custom event name (e.g. "pinglet:addToCart")

dataany

Payload sent as event.detail — object, array, string, number

Example Payload

json{
  "type": "2",
  "projectId": "your-project-id",
  "body": {
    "title": "Flash Sale!",
    "description": "50% off — limited time only",
    "buttons": [
      {
        "text": "Add to Cart",
        "action": "event",
        "event": "pinglet:addToCart",
        "data": {
          "productId": "SKU-123",
          "quantity": 1
        }
      },
      {
        "text": "Maybe Later",
        "action": "close"
      }
    ]
  }
}

Frontend Listener (HTML)

html<script>
  window.addEventListener("pinglet:addToCart", function (e) {
    console.log("Payload:", e.detail);
    addItemToCart(e.detail.productId, e.detail.quantity);
  });
</script>

React / SPA Usage

tsxuseEffect(() => {
  const handler = (e: CustomEvent) => {
    console.log("Event data:", e.detail);
  };
  window.addEventListener("pinglet:addToCart", handler);
  return () => window.removeEventListener("pinglet:addToCart", handler);
}, []);

Important Notes

  • Register your listener — the SDK only fires the event
  • Works with Type 0, Type 1, and Type 2
  • Max 3 buttons per notification
  • Use namespaced names like "app:action" to avoid collisions
  • Don't put sensitive data in the payload

Button Actions Reference

ActionRequired FieldsBehavior
redirectsrc (valid URL)Opens URL in new tab (_blank)
linksrc (valid URL)Opens URL in same tab
alertsrc (string)Shows browser alert(src)
reloadReloads the page
closeDismisses the notification
eventevent (string)Fires CustomEvent on window
onClickonClick (fn string)Evaluates inline function

Overrides & Config

Customize notification appearance and behavior per-request using the overrides object.

PropertyTypeDescription
positionstring"top-right" | "top-left" | "bottom-right" | "bottom-left"
durationnumberAuto-dismiss timer in milliseconds
auto_dismissbooleanIf false, stays until manually closed
transitionstring"fade" | "slide" | "zoom"
maxVisiblenumberMax visible notifications at once
stackingbooleanStack multiple notifications
dismissiblebooleanAllow manual dismiss
sound.playbooleanPlay notification sound
sound.srcstringCustom sound URL
sound.volumenumberVolume 0 to 1
theme.modestring"light" | "dark" | "auto"
theme.roundedbooleanRounded corners
theme.shadowbooleanDrop shadow
progressBar.showbooleanShow progress bar
progressBar.colorstringHex/RGB color
branding.showbooleanShow branding

Full API Schema Reference

Top-level schema for POST /api/v1/notifications/send. Rate limit: 30 req/min.

json{
  "projectId":       "string (exactly 24 chars)",   // REQUIRED
  "type":            ""-1" | "0" | "1" | "2"",       // REQUIRED
  "variant":         "string",                      // optional (type 0 only)
  "tag":             "string",                      // optional
  "overrides":       "OverridesObject",              // optional (premium only)
  "body":            "BodyObject",                   // required for type 0 & 2
  "data":            "BrowserPushObject",             // required for type -1
  "custom_template": "Record<string, any>",          // required for type 1
  "template_id":     "string"                        // required for type 1
}

Validation Rules

  • type "0" → body required, no template_id/custom_template
  • type "2" → body required, no template_id/custom_template/variant
  • type "1" → template_id + custom_template required, no body/data
  • type "-1" → data required, no template_id/custom_template

Body Object (type 0 & 2)

json{
  "title":       "string (min 3 chars)",  // REQUIRED
  "description": "string",
  "icon":        "string",               // emoji/base64 (type 0), URL (type 2)
  "logo":        "string",               // type 0 only
  "url":         "string (valid URL)",
  "media":       { "type": "image|video|audio|iframe", "src": "url" },
  "buttons":     "ButtonSchema[] (max 3)"
}

Button Schema (discriminated union)

json// redirect / link
{ "text": "View", "action": "redirect", "src": "https://..." }

// reload / close
{ "text": "Refresh", "action": "reload" }
{ "text": "Dismiss", "action": "close" }

// alert
{ "text": "Alert", "action": "alert", "src": "Message text" }

// event
{ "text": "Track", "action": "event", "event": "my-event", "data": {} }

// onClick (advanced)
{ "text": "Run", "action": "onClick", "onClick": "() => ..." }

Global Config

When the SDK initializes, it fetches project config from the server. These settings are merged with SDK defaults. Premium overrides are applied per-notification.

jsonGET {endpoint}/load/projects?projectId=xxx&domain=yyy

Response:
{
  "success": true,
  "result": {
    "is_premium": false,
    "config": {
      "position": "bottom-left",
      "transition": "fade",
      "duration": 5000,
      "maxVisible": 3,
      "stacking": true,
      "auto_dismiss": true,
      "dismissible": true,
      "website": true,
      "favicon": true,
      "time": true,
      "sound": { "play": false, "src": "", "volume": 0.5 },
      "theme": { "mode": "auto" },
      "branding": { "show": true, "once": true, "html": "" },
      "progressBar": { "show": true, "color": "" }
    },
    "template": { "config": { ... } }
  }
}

SSE Connection & Lifecycle

The SDK uses Server-Sent Events for real-time notification delivery.

  1. SDK calls: new EventSource(`${endpoint}/sse?projectId=${projectId}&pingletId=${pingletId}`)
  2. Server keeps connection alive with :heartbeat\n\n every 30s
  3. On message: SDK parses JSON, checks parsed.type, dispatches to handler
  4. On error: EventSource auto-reconnects (browser built-in)
  5. On server shutdown: clients reconnect when server comes back
bash// SSE endpoint
GET /api/v1/notifications/sse?projectId=xxx&pingletId=yyy

Stacking & Queue Behavior

Glassmorphism (Type 0 & 2 — unified renderer)

  • • maxVisible = 3 (default, configurable via config or overrides)
  • • When > maxVisible, notifications are QUEUED (FIFO)
  • • When a visible notification is dismissed, the next queued one appears
  • • Queue is per-position (top-right queue is separate from bottom-left)
  • • Tag dedup: same tag replaces the existing notification with smooth crossfade
  • • Each card has its own branding footer, close button, and progress bar
  • • Hover pauses the auto-dismiss timer (always enabled in v0.0.3)

Example — Fire 6 type 2 notifications rapidly

→ 1, 2, 3 appear immediately (maxVisible = 3) → 4, 5, 6 go into queue → User dismisses #1 → #4 appears → User dismisses #2 → #5 appears → etc.

Branding

Branding appears as a footer on all glassmorphism notifications (Type 0, 1, & 2).

  • • Each card has its own branding footer
  • • Server provides branding HTML via project config
  • • Override via branding.html in overrides or dashboard config
  • • Set branding.show = false to hide
  • branding.once = true shows branding only on first notification
json// Custom branding (via overrides or dashboard config)
{
  "branding": {
    "show": true,
    "html": "Powered by <b>Enjoys</b> 🚀"
  }
}

Dark Mode

The SDK supports 3 theme modes.

ModeBehavior
"auto"Follows system prefers-color-scheme
"dark"Always dark
"light"Always light

How It Works

  • All in-app types: .pn-dark class added when dark = true — all sub-elements restyle including branding footer, media players, and buttons
  • "auto" (default): System preference via matchMedia('prefers-color-scheme: dark')
  • "dark": Always dark — .pn-dark class always applied
  • "light": Always light — no dark class
  • Set via: Dashboard config → theme.mode = "auto" or per-notification → overrides.theme.mode
  • v0.0.3: Explicit theme param on showHtmlNotification() — deep-merged with global config

Font & Image Caching

Font

  • • SDK injects Google Fonts Manrope (wght 200..800) via link rel="stylesheet"
  • • Uses display=swap for fast first paint
  • • Browser caches font files automatically (Google Fonts CDN headers)
  • • All SDK elements use font-family: 'Manrope', sans-serif

Image Caching

  • • Icon URLs are prefetched via link rel="prefetch" (all in-app types)
  • • Each unique icon URL is prefetched only once (deduped)
  • • All <img> elements use decoding="async"
  • • Media images use loading="lazy"

Tips for Icon Caching

  • 1. Serve from a CDN with Cache-Control: public, max-age=31536000
  • 2. Use versioned URLs: icon.png?v=2
  • 3. Use a consistent icon URL across notifications (same URL = same cache)

Error Handling

SDK System Popups

The SDK shows system popups (via showPopup()) for:

  • • Missing pingletId
  • • Missing endpoint
  • • Version mismatch
  • • Missing checksum
  • • Failed config load
  • • Failed template load

These are red/amber toast-style popups with retry/docs buttons.

API Error Responses

CodeMeaning
401Invalid or missing project credentials
422Zod validation error (malformed payload)
429Rate limited (30 req/min exceeded)
500Server error

Notes & Gotchas

1.

projectId MUST be exactly 24 characters.

2.

In v0.0.3, Type 0 and Type 2 are identical — both use glassmorphism renderer. Icon allows URLs, emoji, SVG, or base64.

3.

Type -1 uses "data" (not "body"). Type 0/2 use "body" (not "data").

4.

Type 1 requires BOTH template_id AND custom_template. Neither body nor data should be present.

5.

Max buttons: 3 for types 0/2, 2 for type -1 (browser limitation).

6.

Button action "event" requires the "event" field (event name string). The "data" field is optional.

7.

Overrides in v0.0.3 are non-mutating — fresh copy per notification. Premium flag: is_tff. Theme overrides are deep-merged.

8.

SSE auto-reconnects on disconnect. No manual retry needed.

9.

Push permission (type -1) must be granted by the user. The SDK asks automatically on load.

10.

Tag dedup (type 0 &amp; 2): sending same tag replaces the existing notification with smooth crossfade.

11.

Stacking (type 0 &amp; 2): maxVisible defaults to 3. Excess goes to queue. Queue drains on dismiss.

12.

Branding footer is ALWAYS shown unless config.branding.show = false.

13.

Font: Manrope is loaded from Google Fonts with display=swap.

14.

Images with the same URL are prefetched only once (deduped by SDK).

15.

Rate limit: 30 notifications/minute per project. 429 = throttled.