Skip to content
Docs

PulseKit documentation

One HTTP call, multi-channel delivery, retries, rate limiting, and a real-time audit trail. Everything below matches the live API and SDK contracts.

01

Installation

The SDK talks to the hosted API. A project API key is the only credential you need — it is shown exactly once when you create a project.

bash
npm install pulsekit-sdk

Published as pulsekit-sdk (v0.1.1) — works with any Node 18+ runtime, no framework required.

02

Quickstart

Create a project, copy the API key, then send your first event. The call returns a receipt or — on transient failure — null, so your own retry policy is one branch.

TypeScript
import { PulseKit, PulseKitError } from 'pulsekit-sdk'

const pulse = new PulseKit({ apiKey: 'pk_test_...' })

const receipt = await pulse.notify({
  event:    'payment.failed',
  user:     'user_123',
  data:     { amount: 499, reason: 'card_declined' },
  userName: 'Priya',          // optional — email greeting
})
// → { eventId: '3f2c…', receivedAt: '2026-09-18T18:00:00.000Z' }
// → null on transient failure (5xx / 429 / network / timeout)

A 4xx response throws PulseKitError — the caller made a mistake, fix the request. Anything transient (429, 5xx, network, timeout) returns null.

03

SDK reference

new PulseKit(options) takes an apiKey (required), an optional baseUrl (default the hosted API), and a 10s request timeout.

FieldTypeRequiredSent asNotes
eventstringyesevent_name
userstringyesuser_idNever shown in emails
dataobject—payloadDefaults to {}
tostring—toPer-event email recipient override
userNamestring—user_nameEmail greeting; stored on the email delivery row, never on the event
04

REST API

Ingest an event with POST /api/v1/events, authenticating with the project key via a Bearer header.

bash
curl -X POST https://api.getpulsekit.cloud/api/v1/events \
  -H "Authorization: Bearer pk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "event_name": "payment.failed",
    "user_id":    "user_123",
    "payload":    { "amount": 499, "reason": "card_declined" }
  }'
response
// 202 Accepted
{
  "success": true,
  "data": {
    "eventId":    "3f2c1a…",
    "receivedAt": "2026-09-18T18:00:00.000Z"
  }
}

If the project has zero channels enabled, the event is still accepted — but data also carries warning: "no_channels_enabled" so the caller learns nothing will deliver.

CodeMeaning
202Event accepted and enqueued
400event_name or user_id missing
401Invalid or missing API key
429Rate limit exceeded — Retry-After: 60
500Server error
05

Rate limiting

Sliding-window rate limiting, enforced per project by an atomic Redis Lua script — not per IP.

SettingValue
Default30 requests / minute per project
Configurable5–30 req/min
When blocked429 with Retry-After: 60, consumes no quota
06

Delivery channels

Channels are enabled per project in the dashboard. The worker only delivers to channels that are explicitly configured.

ChannelDelivery mechanismNotes
EmailResendBranded HTML, humanised payload, optional userName greeting; resolved recipient stored per attempt
SlackIncoming webhookRedirects are treated as failures — no false delivered logs
In-appRow inserted into notificationsUnread count, mark-as-read and mark-all-read via PATCH

A per-event to overrides the email recipient for that event only. Failures on one channel never re-deliver the others.

07

Retries & audit

Channel-level failures (Resend rejects, Slack 4xx) are logged once as failed, never retried. Catastrophic failures retry up to five times with exponential backoff and jitter, then move to a dead-letter queue with a sentinel failed row.

delivery_logs is append-only — failures and give-ups are recorded, never updated.

Delivery statuses: pending · delivered · failed · rate_limited · deduplicated (reserved)

08

Real-time feed

The WebSocket server shares the Express HTTP server on the same port wss://api.getpulsekit.cloud. The worker publishes a delivery_update message to Redis after every attempt; the dashboard's LiveFeed filters by project and reconnects with backoff.

Connecting directly from your own frontend is not part of the current API surface — the live feed is a dashboard feature.