npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@enkaliprime/kinetic-sdk

v0.3.0

Published

Official JavaScript/TypeScript SDK for the Kinetic Developer API — by EnkaliPrime

Readme

@enkaliprime/kinetic-sdk

Official JavaScript/TypeScript SDK for the Kinetic WhatsApp Developer API

Send WhatsApp messages from Node.js, browsers, Next.js, React Native, Expo, and Electron — with full TypeScript support and zero runtime dependencies.

npm version License TypeScript Node


Features

  • WhatsApp messaging API — send text messages through your linked Kinetic business line
  • Group messaging — list participating groups and post alerts to WhatsApp groups
  • Webhooks — verify signed inbound events and test delivery to your endpoints
  • Bearer authenticationkin_live_ / kin_test_ API keys from the Kinetic Developer Console
  • Fetch-only HTTP client — works everywhere fetch is available (Node 18+, browsers, React Native, Expo)
  • Zero dependencies — no React, no axios, no Node-only modules in the core package
  • Typed errorsKineticAuthError, KineticValidationError, KineticApiError, KineticNetworkError
  • Injectable fetch — custom implementations for tests, polyfills, or React Native
  • Configurable timeout — abort requests after timeoutMs (default 30s)

Installation

npm install @enkaliprime/kinetic-sdk
yarn add @enkaliprime/kinetic-sdk
pnpm add @enkaliprime/kinetic-sdk

Requirements: Node.js 18+ (for native fetch), or any environment with a fetch polyfill.


Quick start

1. Get an API key

  1. Sign in at kinetic.africa
  2. Open Developers → API Keys
  3. Create a key with Send Messages (and Group Messaging if you post to groups)
  4. Copy the kin_live_… secret when shown (it is only displayed once)

2. Send your first message

import { Kinetic } from "@enkaliprime/kinetic-sdk";

const kinetic = new Kinetic({
  apiKey: process.env.KINETIC_API_KEY!,
});

const result = await kinetic.messages.send({
  to: "+264811234567",
  message: "Weekly report generated.",
});

console.log(result.id);     // msg_a1b2c3d4e5f6
console.log(result.status); // queued

3. Environment variable (recommended)

KINETIC_API_KEY=kin_live_your_secret_here

Never commit API keys to source control. Use environment variables or a secrets manager.


Client configuration

import { Kinetic } from "@enkaliprime/kinetic-sdk";

const kinetic = new Kinetic({
  apiKey: "kin_live_xxxxxxxxxxxxxxxxxxxxxxxx",
  baseUrl: "https://kinetic-4cfj.onrender.com", // optional — this is the default
  timeoutMs: 30_000,                             // optional — request timeout
  fetch: globalThis.fetch,                       // optional — custom fetch impl
});

| Option | Required | Default | Description | |--------|----------|---------|-------------| | apiKey | Yes | — | Your kin_live_ or kin_test_ API key | | baseUrl | No | https://kinetic-4cfj.onrender.com | Kinetic API base URL (no trailing slash) | | fetch | No | globalThis.fetch | Custom fetch for tests or React Native | | timeoutMs | No | 30000 | Abort request after this many milliseconds |

Local development

Point at a local WhatsApp gateway while developing:

const kinetic = new Kinetic({
  apiKey: process.env.KINETIC_API_KEY!,
  baseUrl: "http://localhost:3001",
});

Run the gateway with npm run gateway:dev in the aura-chat project.


API reference

Kinetic

Main client. Exposes messages, groups, and webhooks resources.

const kinetic = new Kinetic({ apiKey: "kin_live_..." });

kinetic.messages.send(input)

Send a text message to a WhatsApp number via your linked business line.

Parameters

| Field | Type | Description | |-------|------|-------------| | to | string | E.164 phone number (e.g. +264811234567) | | message | string | Message body (plain text) |

Returns Promise<SendMessageResponse>

type SendMessageResponse = {
  id: string;                        // e.g. msg_a1b2c3d4e5f6
  status: "queued" | "sent";
};

Example

const { id, status } = await kinetic.messages.send({
  to: "+264813523159",
  message: "Your appointment is confirmed for tomorrow at 10:00.",
});

HTTP mapping

POST /v1/messages/send
Authorization: Bearer kin_live_...
Content-Type: application/json

{ "to": "+264...", "message": "..." }

The business is inferred from your API key — do not send businessId in the request body.

kinetic.groups.list()

List WhatsApp groups your linked business account participates in.

Requires group_messaging on your API key.

Returns Promise<ListGroupsResponse>

type WhatsAppGroup = {
  id: string;           // e.g. [email protected]
  name: string;
  participants: number;
};

type ListGroupsResponse = {
  groups: WhatsAppGroup[];
};

Example

const { groups } = await kinetic.groups.list();
for (const group of groups) {
  console.log(group.id, group.name, group.participants);
}

HTTP mapping

GET /v1/groups
Authorization: Bearer kin_live_...

kinetic.groups.send(input)

Send a text message to a WhatsApp group.

Requires group_messaging on your API key.

Parameters

| Field | Type | Description | |-------|------|-------------| | group_id | string | Group JID (120363…@g.us) or numeric id | | message | string | Message body (plain text) |

Returns Promise<SendGroupMessageResponse>

type SendGroupMessageResponse = {
  id: string;           // e.g. grp_a1b2c3d4e5f6
  status: "queued" | "sent";
};

Example

const { id, status } = await kinetic.groups.send({
  group_id: "[email protected]",
  message: "Safety alert: weekly report is ready.",
});

HTTP mapping

POST /v1/groups/send
Authorization: Bearer kin_live_...
Content-Type: application/json

{ "group_id": "120363…@g.us", "message": "..." }

Group delivery with direct-message fallback

For safety or ops alerts, prefer the group when you have a group_id. If group delivery fails (missing permission, bot not in group, WhatsApp disconnected), fall back to direct messages — for example numbers in KINETIC_SAFETY_FALLBACK_PHONES:

async function sendSafetyAlert(
  kinetic: Kinetic,
  groupId: string,
  message: string,
  fallbackPhones: string[],
) {
  try {
    return await kinetic.groups.send({ group_id: groupId, message });
  } catch (err) {
    console.warn("Group send failed, falling back to direct messages", err);
    for (const to of fallbackPhones) {
      await kinetic.messages.send({ to, message });
    }
  }
}

Use kinetic.groups.list() to discover valid group_id values for your linked line.

kinetic.webhooks.test(input?)

Send a signed test message.sent event to your registered webhook endpoint(s).

Parameters

| Field | Type | Description | |-------|------|-------------| | webhook_id | string | Optional — target one webhook; omit to ping all active endpoints subscribed to message.sent |

Returns Promise<TestWebhookResponse>

type TestWebhookResponse = {
  queued: number; // delivery rows queued
};

Example

// Ping all active webhooks for this business
const { queued } = await kinetic.webhooks.test();

// Ping a specific endpoint
await kinetic.webhooks.test({ webhook_id: "your-webhook-uuid" });

HTTP mapping

POST /v1/webhooks/test
Authorization: Bearer kin_live_...
Content-Type: application/json

{}  // or { "webhook_id": "..." }

Webhooks — receive events

When you register a webhook URL in the Developer Console, Kinetic POSTs signed JSON events to your server.

Event envelope

{
  "id": "evt_a1b2c3d4e5f67890",
  "type": "message.sent",
  "created_at": "2026-06-20T12:00:00.000Z",
  "data": {
    "message_id": "msg_a1b2c3d4e5f6",
    "to": "+264811234567",
    "status": "queued"
  }
}

Event types

| Type | When fired | |------|------------| | message.sent | Outbound message accepted by the API | | message.failed | Outbound send failed | | message.delivered | Delivery receipt (coming soon) | | message.read | Read receipt (coming soon) | | message.incoming | Inbound direct message (coming soon) | | group.reply | Inbound group message (coming soon) | | media.received | Inbound media (coming soon) | | ai.handover | AI handover (coming soon) |

Subscribe to events when creating a webhook in the console. Each endpoint has a signing secret (shown once at creation).

Verify signatures

Kinetic signs each request with HMAC-SHA256:

X-Kinetic-Signature: t=<unix_seconds>,v1=<hex_hmac>
X-Kinetic-Event-Id: evt_...
X-Kinetic-Event-Type: message.sent

Signed payload: ${timestamp}.${rawBody} using your webhook signing secret.

import {
  verifyAndParseWebhook,
  verifyWebhookSignature,
  parseWebhookEvent,
  KineticWebhookSignatureError,
  WEBHOOK_SIGNATURE_HEADER,
  type MessageSentEventData,
  type MessageFailedEventData,
} from "@enkaliprime/kinetic-sdk";

// Recommended — verify + parse in one call
const event = await verifyAndParseWebhook<MessageSentEventData>({
  secret: process.env.KINETIC_WEBHOOK_SECRET!,
  rawBody: requestBodyString,
  signatureHeader: req.headers["x-kinetic-signature"],
  toleranceSec: 300, // optional — default 5 minutes
});

switch (event.type) {
  case "message.sent":
    console.log("Sent:", event.data.message_id, event.data.to ?? event.data.group_id);
    break;
  case "message.failed":
    console.error("Failed:", event.data.error);
    break;
}

Lower-level helpers:

const ok = await verifyWebhookSignature(secret, rawBody, signatureHeader);
const event = parseWebhookEvent(rawBody); // parse only — verify first in production

Express webhook handler

Use the raw body (not express.json() alone) so the signature matches:

import express from "express";
import {
  verifyAndParseWebhook,
  KineticWebhookSignatureError,
} from "@enkaliprime/kinetic-sdk";

const app = express();

app.post(
  "/webhooks/kinetic",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    try {
      const rawBody = req.body.toString("utf8");
      const event = await verifyAndParseWebhook({
        secret: process.env.KINETIC_WEBHOOK_SECRET!,
        rawBody,
        signatureHeader: req.headers["x-kinetic-signature"] as string,
      });

      // Handle event asynchronously if needed
      console.log(event.type, event.data);
      res.sendStatus(200);
    } catch (err) {
      if (err instanceof KineticWebhookSignatureError) {
        return res.status(401).json({ error: err.message });
      }
      res.status(500).json({ error: "Webhook handler failed" });
    }
  },
);

Next.js App Router

// app/api/webhooks/kinetic/route.ts
import { verifyAndParseWebhook, KineticWebhookSignatureError } from "@enkaliprime/kinetic-sdk";
import { NextResponse } from "next/server";

export async function POST(req: Request) {
  const rawBody = await req.text();
  try {
    const event = await verifyAndParseWebhook({
      secret: process.env.KINETIC_WEBHOOK_SECRET!,
      rawBody,
      signatureHeader: req.headers.get("x-kinetic-signature"),
    });
    console.log(event.type, event.data);
    return NextResponse.json({ received: true });
  } catch (err) {
    if (err instanceof KineticWebhookSignatureError) {
      return NextResponse.json({ error: err.message }, { status: 401 });
    }
    throw err;
  }
}

Framework examples

Node.js (ESM)

import { Kinetic } from "@enkaliprime/kinetic-sdk";

const kinetic = new Kinetic({ apiKey: process.env.KINETIC_API_KEY! });

await kinetic.messages.send({
  to: "+264811234567",
  message: "Hello from Node",
});

Next.js API route

// app/api/notify/route.ts
import { Kinetic, KineticError } from "@enkaliprime/kinetic-sdk";
import { NextResponse } from "next/server";

const kinetic = new Kinetic({ apiKey: process.env.KINETIC_API_KEY! });

export async function POST(req: Request) {
  const { to, message } = await req.json();

  try {
    const result = await kinetic.messages.send({ to, message });
    return NextResponse.json(result);
  } catch (err) {
    if (err instanceof KineticError) {
      return NextResponse.json({ error: err.message }, { status: err.status || 500 });
    }
    throw err;
  }
}

Express

import express from "express";
import { Kinetic, KineticAuthError } from "@enkaliprime/kinetic-sdk";

const app = express();
const kinetic = new Kinetic({ apiKey: process.env.KINETIC_API_KEY! });

app.post("/notify", express.json(), async (req, res) => {
  try {
    const result = await kinetic.messages.send(req.body);
    res.json(result);
  } catch (err) {
    if (err instanceof KineticAuthError) {
      return res.status(401).json({ error: err.message });
    }
    res.status(500).json({ error: "Send failed" });
  }
});

React Native / Expo

import { Kinetic } from "@enkaliprime/kinetic-sdk";

const kinetic = new Kinetic({
  apiKey: process.env.EXPO_PUBLIC_KINETIC_API_KEY!,
  // fetch is available globally in modern RN / Expo
});

await kinetic.messages.send({
  to: "+264811234567",
  message: "Hello from the app",
});

Raw cURL (no SDK)

curl -X POST "https://kinetic-4cfj.onrender.com/v1/messages/send" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer kin_live_YOUR_KEY" \
  -d '{"to":"+264811234567","message":"Hello from cURL"}'

Error handling

All API errors extend KineticError:

import {
  Kinetic,
  KineticAuthError,
  KineticValidationError,
  KineticApiError,
  KineticNetworkError,
  KineticWebhookSignatureError,
  KineticError,
} from "@enkaliprime/kinetic-sdk";

try {
  await kinetic.messages.send({ to: "+264...", message: "Hi" });
} catch (err) {
  if (err instanceof KineticAuthError) {
    // 401 — invalid, revoked, or missing API key
    console.error("Check your kin_live_ key");
  } else if (err instanceof KineticValidationError) {
    // 400 — bad phone number, empty message, etc.
    console.error("Invalid request:", err.message);
  } else if (err instanceof KineticApiError) {
    // 5xx — server or WhatsApp session error
    console.error("API error:", err.status, err.message);
  } else if (err instanceof KineticNetworkError) {
    // timeout, DNS, offline
    console.error("Network error:", err.message);
  } else if (err instanceof KineticError) {
    console.error(err.status, err.message, err.body);
  }
}

| Error class | HTTP status | Typical cause | |-------------|-------------|---------------| | KineticAuthError | 401 | Missing/invalid Bearer token, revoked key | | KineticValidationError | 400 | Missing to or message, malformed body | | KineticApiError | 4xx/5xx | Server errors, WhatsApp not connected | | KineticNetworkError | — | Timeout, connection refused, DNS failure | | KineticWebhookSignatureError | — | Invalid or missing webhook signature / payload |


TypeScript

Full types are exported from the package:

import type {
  KineticClientOptions,
  ListGroupsResponse,
  SendGroupMessageInput,
  SendGroupMessageResponse,
  SendMessageInput,
  SendMessageResponse,
  WhatsAppGroup,
  KineticErrorBody,
  WebhookEvent,
  WebhookEventType,
  MessageSentEventData,
  MessageFailedEventData,
  TestWebhookInput,
  TestWebhookResponse,
} from "@enkaliprime/kinetic-sdk";

API keys & permissions

Keys are created in the Kinetic Developer Console and prefixed by environment:

| Prefix | Environment | |--------|-------------| | kin_live_ | Production | | kin_test_ | Test (when enabled for your workspace) |

Each key has granular permissions:

| Permission | Description | |------------|-------------| | send_messages | Required for messages.send() | | group_messaging | Required for groups.list() and groups.send() | | templates | Template messages (coming soon) | | media | Media messages (coming soon) |

Keys are stored as SHA-256 hashes server-side. The full secret is shown once at creation or rotation.


Roadmap

Shipped in v0.2.x:

kinetic.messages.send({ to, message });
kinetic.groups.list();
kinetic.groups.send({ group_id, message });
kinetic.webhooks.test({ webhook_id? });
verifyAndParseWebhook({ secret, rawBody, signatureHeader });

Shipped in v0.1.x:

kinetic.messages.send({ to, message });
kinetic.groups.list();
kinetic.groups.send({ group_id, message });

Planned:

// kinetic.media.send({ to, url, caption });
// kinetic.templates.send({ ... });
// Inbound events: message.incoming, message.delivered, message.read

Development

Clone the sdk monorepo:

cd kinetic-sdks/packages/sdk
npm install
npm run build
npm test

Support


License

MIT © EnkaliPrime