Node SDK
Careful: This package is coming soon and not published yet, so the install command below does not work today. Until it is, use the plain HTTP request from the quickstart.
One call from any Node server. No dependencies, Node 18 or newer, ESM and CommonJS, TypeScript types included.
Careful: Server-side only. Never put an API key in browser or mobile code.
Install
npm install pingloom
export PINGLOOM_API_KEY=<your key>
export PINGLOOM_BASE_URL=https://api.pingloom.devSet PINGLOOM_BASE_URL to the PingLoom API origin shown in your dashboard. The SDKs have no built-in default, so a key can never be sent to the wrong host by accident. The same package ships the pingloom command line tool.
Send
send() returns the event ID. Only title is required. The limits are the same as the API reference: title 120 characters, body 2,000, data 8 KB.
import { PingLoom } from "pingloom"; // or: const { PingLoom } = require("pingloom");
const pl = new PingLoom(); // reads PINGLOOM_API_KEY and PINGLOOM_BASE_URL
const eventId = await pl.send({
title: "Payment received: $49.00",
body: "Order 1042 was paid.",
channel: "payments",
priority: "normal",
data: { order_id: "1042" },
actionUrl: "https://example.com/orders/1042",
idempotencyKey: "order-1042-paid",
});Without idempotencyKey the SDK generates a random one per call, so its own retries never create duplicates. Pass a key derived from the business event, like order-1042-paid, to make repeated calls of your own safe too.
Check the status
status() needs a key with the events:read scope.
const event = await pl.status(eventId);
console.log(event.status); // provider_accepted means FCM accepted itErrors and retries
Every API error becomes a typed error with status, code, message, requestId and details. Options: maxRetries (default 3), backoffMs, timeoutMs. Retries use exponential backoff or Retry-After and always reuse the same idempotency key.
| API response | Exception | Retried automatically |
|---|---|---|
| 400, 413, 415, 422 | InvalidRequestError | No. Fix the request, see details. |
| 401 | AuthenticationError | No. The key is missing, revoked or invalid. |
| 403 | PermissionDeniedError | No. The key lacks the scope. |
| 404 | NotFoundError | No. |
| 409 | ConflictError | No. Same idempotency key with a different body. |
| 429 RATE_LIMITED | RateLimitError | Yes, after Retry-After. |
| 429 QUOTA_EXCEEDED | QuotaExceededError | No. The monthly quota is used. |
| 5xx | ServerError | Yes, with the same idempotency key. |
| No response | NetworkError | Yes. |
| Missing key or base URL | ConfigurationError | Not sent. |
import { PingLoom, QuotaExceededError, PingLoomError } from "pingloom";
try {
await pl.send({ title: "Backup finished" });
} catch (e) {
if (e instanceof QuotaExceededError) {
// the monthly quota is used; do not retry
} else if (e instanceof PingLoomError) {
console.error(e.status, e.code, e.requestId); // the key is never in the message
}
}The key is masked when you print the client, in error text and in logs as pl_live_****abcd.