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

@datamo/sdk

v0.4.1

Published

SDK para integrar tu aplicación con Datamo — pull de métricas, webhooks y push en tiempo real

Readme

@datamo/sdk

SDK oficial de Datamo para integrar tu aplicación SaaS.

Datamo es una plataforma de inteligencia unificada que permite a los usuarios ver métricas de todas sus herramientas en un solo lugar. Con este SDK, tu SaaS puede exponer métricas hacia Datamo mediante dos mecanismos complementarios:

  • Pull: Datamo sondea tu API periódicamente para obtener métricas actualizadas
  • Push: Tu app envía métricas a Datamo en tiempo real cuando ocurre un evento

Instalación

npm install @datamo/sdk

Requiere Node.js ≥ 18. createDatamoRouter necesita Express ≥ 4.18 (peer dependency opcional, no se instala automáticamente).

Funciona con require (CommonJS) y con import (ESM) desde 0.4.0:

import { DatamoPushClient } from '@datamo/sdk'        // ESM
const { DatamoPushClient } = require('@datamo/sdk')   // CommonJS

¿Vienes de 0.2.x? Lee la guía de migración a 0.4.1: ^0.2.0 no sube solo a 0.4 (es 0.x), y desde el 2026-11-29 Datamo deja de aceptar el push con la firma antigua.

Aviso (0.4.0): la firma legacy del push (X-Datamo-Signature, con el webhookToken como clave) dejará de aceptarse el 2026-11-29. Pasa tu clientSecret a DatamoPushClient para firmar también con X-Datamo-Signature-V2. Ver Firma V2 del push.


Cómo funciona

Registro de la integración

Antes de usar el SDK, el desarrollador debe registrar su aplicación en el portal de desarrolladores de Datamo (datamo.app/developer). Al registrar:

  1. Se genera un clientId y clientSecret. El clientSecret se pasa al SDK (opts.clientSecret) para verificar que las notificaciones de conexión vienen de Datamo
  2. Se configura la apiBaseUrl: URL base de tu API donde Datamo hará las llamadas
  3. Se define el credentialSchema: qué datos pedirle al usuario al conectar (API key, usuario/contraseña, etc.)
  4. Se establece el syncIntervalMinutes: cada cuántos minutos Datamo hace el pull (mínimo 5)

Flujo de conexión

Cuando un usuario de Datamo activa tu integración desde el marketplace:

Usuario activa integración en Datamo
         │
         ▼
Datamo cifra las credenciales del usuario
         │
         ▼
Datamo genera un webhookToken único para esta conexión
         │
         ├──▶ POST {apiBaseUrl}/datamo/connect
         │    Body: { workspaceId, webhookUrl, webhookToken, scopes }
         │    ← Tu app guarda webhookUrl y webhookToken por workspaceId
         │
         └──▶ Datamo comienza a sondear GET {apiBaseUrl}/datamo/metrics
              Headers: X-Datamo-Workspace, X-Datamo-Credentials, X-Datamo-Signature, X-Datamo-Timestamp
              ← Tu app verifica la firma y retorna las métricas

El webhookToken es único por conexión (por cada usuario que conecta tu integración). Debes guardarlo en tu base de datos indexado por workspaceId. Es necesario para:

  • Verificar la autenticidad de los pull requests de Datamo (HMAC-SHA256)
  • Firmar los eventos push que envíes a Datamo

Configuración en tu servidor Express

import express from 'express'
import { createDatamoRouter } from '@datamo/sdk/express'

const app = express()

// Ruta propia: el apiBaseUrl registrado en Datamo es https://api.tuapp.cl/integrations
app.use('/integrations', createDatamoRouter({
  // El clientSecret de tu app (portal de desarrolladores de Datamo): exige la
  // firma de Datamo en /datamo/connect.
  clientSecret: process.env.DATAMO_CLIENT_SECRET,

  // Datamo firma sus requests con HMAC usando el webhookToken de cada workspace.
  // Esta función le dice al SDK qué token usar para verificar cada request.
  // Retorna null si el workspace no tiene una conexión activa.
  getWebhookToken: async (workspaceId: string) => {
    const conn = await db.findOne({ workspaceId })
    return conn?.webhookToken ?? null
  },

  // Datamo llama a esta función cada N minutos para obtener métricas actualizadas.
  // credentials contiene lo que el usuario ingresó al conectar la integración.
  onMetrics: async ({ workspaceId, credentials }) => {
    const data = await db.getMetricsForTenant(credentials.tenant_id)
    return [
      { key: 'usuarios_activos', label: 'Usuarios activos',  value: data.activeUsers },
      { key: 'mrr_usd',          label: 'MRR',               value: data.mrr, unit: 'USD' },
      { key: 'tasa_retencion',   label: 'Tasa de retención', value: data.retention, unit: '%' },
    ]
  },

  // Datamo llama a esta función cuando un usuario conecta la integración.
  // IMPORTANTE: guarda AMBOS — webhookToken para verificar pulls, webhookUrl para push.
  onConnect: async ({ workspaceId, webhookUrl, webhookToken, scopes }) => {
    await db.upsert({ workspaceId }, { webhookUrl, webhookToken })
    console.log(`Workspace ${workspaceId} conectó Datamo. Scopes: ${scopes.join(', ')}`)
  },
}))

app.listen(3000)

Esto atiende dos rutas bajo donde lo montes (en el ejemplo, /integrations):

  • GET /datamo/metrics — endpoint que Datamo sondea para obtener métricas
  • POST /datamo/connect — endpoint que Datamo llama al conectar un usuario

Desde 0.4.1:

  • No necesitas express.json(): el SDK lee el JSON solo de /datamo/connect (si ya usas express.json(), reutiliza ese body).
  • No toca otras rutas: cualquier otra request pasa de largo sin que se lea su body, así que montarlo en una ruta compartida (/api) ya no rompe webhooks que necesitan el body crudo. Igual recomendamos una ruta propia.
  • Express no se carga en tiempo de ejecución: @datamo/sdk/express (y @datamo/sdk, que lo sigue exportando por compatibilidad) solo usan sus tipos. DatamoPushClient y los helpers funcionan sin express.

Referencia de la API

createDatamoRouter(opts)

Crea un Express Router con los endpoints necesarios para Datamo.

opts.getWebhookToken(workspaceId: string): Promise<string | null> | string | null

Función que retorna el webhookToken almacenado para un workspaceId. El SDK la llama en cada pull request para verificar la firma HMAC. Retorna null si no existe conexión → Datamo recibe 404.

opts.onMetrics(ctx): Promise<DatamoMetric[]> | DatamoMetric[]

Handler invocado cuando Datamo hace pull de métricas. Recibe:

| Campo | Tipo | Descripción | |---|---|---| | ctx.workspaceId | string | ID del workspace en Datamo | | ctx.credentials | Record<string, string> | Credenciales descifradas del usuario |

Debe retornar un array de DatamoMetric:

| Campo | Tipo | Requerido | Descripción | |---|---|---|---| | key | string | ✅ | Identificador único en snake_case. Ej: "mrr_usd" | | value | number | ✅ | Valor numérico de la métrica | | label | string | No | Nombre legible. Si se omite, Datamo usa el key | | unit | string | No | Unidad. Ej: "USD", "ms", "%" | | recorded_at | string | No | ISO 8601. Si se omite, Datamo usa la hora de ingesta |

opts.onConnect(payload): Promise<void> | void

Callback opcional invocado cuando un usuario conecta la integración. Recibe DatamoConnectPayload:

| Campo | Tipo | Descripción | |---|---|---| | payload.workspaceId | string | ID del workspace en Datamo | | payload.webhookUrl | string | URL para enviar eventos push. Guardar en DB por workspaceId | | payload.webhookToken | string | Token secreto para firmar/verificar. Guardar en DB por workspaceId | | payload.scopes | string[] | Permisos que el usuario aprobó |

opts.clientSecret?: string (desde 0.3.0)

El clientSecret de tu app. Si se configura, POST /datamo/connect exige el header X-Datamo-Connect-Signature (t=<unix>,v1=<hex>): un HMAC-SHA256 con el clientSecret sobre ${t}.${workspaceId}.${webhookToken}.${webhookUrl}.${scopes.join(',')}, con una tolerancia de 5 minutos. Si falta o no es válida, el SDK responde 401 y no llama a onConnect.

Si se omite, el connect se acepta sin firma (comportamiento anterior a 0.3.0) y el SDK muestra una advertencia al arrancar. Recomendado siempre. Las apps registradas antes de este cambio deben rotar su clientSecret una vez para que Datamo empiece a firmar.


opts.onDisconnect(payload): Promise<void> | void (desde 0.4.1)

Datamo llama a POST /datamo/disconnect cuando la empresa desconecta la integración (con reintentos si tu servidor no responde). Recibe { workspaceId }: borra el webhookToken y el webhookUrl guardados para ese workspace. Con clientSecret, el SDK exige X-Datamo-Disconnect-Signature (t=<unix>,v1= HMAC-SHA256 con el clientSecret sobre disconnect.<t>.<workspaceId>); sin él, la firma legacy con el webhookToken.

Firma del pull con el clientSecret (desde 0.4.1)

Además de la firma legacy (con el webhookToken), Datamo firma cada GET /datamo/metrics con X-Datamo-Pull-Signature (t=<unix>,v1= HMAC-SHA256 con el clientSecret sobre pull.<t>.<workspaceId>.<X-Datamo-Credentials>). Con clientSecret configurado, si el header viene, el SDK lo exige válido. Helpers: verifyPullSignature, verifyDisconnectSignature.

DatamoPushClient

Cliente para enviar métricas a Datamo en tiempo real (push model). Usar cuando necesitas notificar a Datamo de un evento inmediatamente, sin esperar el próximo ciclo de pull.

import { DatamoPushClient } from '@datamo/sdk'

// Ejemplo: cuando ocurre una venta en tu app
async function onNewSale(workspaceId: string, amount: number) {
  const conn = await db.findOne({ workspaceId })
  if (!conn?.webhookUrl) return  // este workspace no tiene Datamo conectado

  const push = new DatamoPushClient({
    webhookUrl:   conn.webhookUrl,    // guardado en onConnect
    webhookToken: conn.webhookToken,  // guardado en onConnect
    clientSecret: process.env.DATAMO_CLIENT_SECRET,  // firma V2 (desde 0.4.0)
  })

  await push.send([
    { key: 'nueva_venta',  value: 1,      unit: 'ventas' },
    { key: 'ingresos_hoy', value: amount, unit: 'CLP' },
  ])
}

Opciones

| Opción | Tipo | Descripción | |---|---|---| | webhookUrl | string | URL recibida en onConnect | | webhookToken | string | Token recibido en onConnect | | clientSecret | string? | El clientSecret de tu app (el mismo de createDatamoRouter). Con él se envía X-Datamo-Signature-V2. Obligatorio en la práctica desde el 2026-11-29 |

Si creas el cliente sin clientSecret, el SDK muestra una advertencia (una vez por proceso) y solo envía la firma legacy.

El clientSecret es uno por app, no por conexión: guárdalo como variable de entorno (DATAMO_CLIENT_SECRET), nunca en el repositorio ni en el frontend.

push.send(metrics: DatamoMetric[]): Promise<DatamoPushResult>

Envía hasta 100 métricas en un solo request. Firma el payload automáticamente con HMAC-SHA256: la firma legacy con el webhookToken y, si configuraste clientSecret, la V2. Lanza un error si Datamo responde con código HTTP de error.


Firma V2 del push (desde 0.4.0)

La firma legacy usa como clave el webhookToken, que también viaja en la URL del webhook (/webhook/<token>/events): quien vea la URL puede firmar. La V2 usa como clave el clientSecret de tu app, que nunca viaja en la URL.

X-Datamo-Signature-V2 = hex( HMAC-SHA256(
  key = clientSecret,
  msg = "v2." + webhookToken + "." + timestamp + "." + rawBody
) )
  • timestamp es el valor exacto del header X-Datamo-Timestamp (ISO 8601).
  • rawBody son los bytes exactos del body HTTP enviado.

Transición:

| Periodo | Qué acepta Datamo | |---|---| | Hasta el 2026-11-28 | V2 (si viene, se exige y valida) o solo legacy (se acepta con aviso Deprecation) | | Desde el 2026-11-29 | Solo requests con X-Datamo-Signature-V2 válida |

Durante la transición DatamoPushClient envía ambos headers. Si construyes el request a mano, usa los helpers:

import { signPushV2, pushSigningPayloadV2 } from '@datamo/sdk'

const timestamp = new Date().toISOString()
const body = JSON.stringify({ metrics })
const signatureV2 = signPushV2({ clientSecret, webhookToken, timestamp, rawBody: body })
// headers: X-Datamo-Timestamp: timestamp, X-Datamo-Signature-V2: signatureV2

Vector de prueba:

| Entrada | Valor | |---|---| | clientSecret | cs_test_datamo_v2 | | webhookToken | whk_test_token | | timestamp | 2026-09-30T12:00:00.000Z | | rawBody | {"metrics":[{"key":"nueva_venta","value":1}]} | | Firma V2 esperada | e2ad6447ca745c77f9b494fdc0774f54e0bb827a494df4413443b51bb1da205e |


Headers que envía Datamo en los pull requests

Cuando Datamo llama a GET /datamo/metrics, incluye estos headers:

| Header | Descripción | |---|---| | X-Datamo-Timestamp | ISO 8601 — momento del request | | X-Datamo-Workspace | ID del workspace que se está sincronizando | | X-Datamo-Credentials | JSON base64 con las credenciales descifradas del usuario | | X-Datamo-Signature | HMAC-SHA256 del request para verificar autenticidad |

El SDK verifica automáticamente la firma y el timestamp: lo acepta dentro de ±5 minutos de tu reloj (desde 0.4.1 la tolerancia es simétrica; antes un reloj de Datamo adelantado en 1 s daba 401).

Algoritmo de firma:

signature = HMAC-SHA256(webhookToken, `${timestamp}.${workspaceId}`)

Algoritmo de firma para push (DatamoPushClient → Datamo):

X-Datamo-Signature    = HMAC-SHA256(webhookToken, `${timestamp}.${rawBody}`)                   // legacy, hasta 2026-11-28
X-Datamo-Signature-V2 = HMAC-SHA256(clientSecret, `v2.${webhookToken}.${timestamp}.${rawBody}`)  // desde 0.4.0

Importante si no usas este SDK para enviar el push (Python, Go, curl, etc.): Datamo verifica la firma sobre los bytes exactos del body HTTP, no sobre una re-serialización de { metrics } en el servidor. Firma exactamente la misma cadena de texto que vas a enviar como body — cualquier diferencia de orden de claves, espaciado, formato de números (1.0 vs 1) o escapado de unicode entre lo que firmas y lo que envías hará que la firma no coincida. DatamoPushClient.send() ya lo hace bien (firma la misma variable body que envía); si construyes el request a mano, asegúrate de firmar el string final, no un objeto que luego serializas de nuevo.


Definición del credentialSchema en el portal de Datamo

Al registrar tu app en el portal de desarrolladores, defines qué campos le pedirle al usuario al conectar la integración. Estos valores llegan descifrados en ctx.credentials en cada call a onMetrics.

Ejemplo para una app con API key por tenant:

authType: api_key
fields:
  - key: tenant_id   | label: ID de tu cuenta    | type: text     | required: true
  - key: api_key     | label: API Key             | type: password | required: true

El usuario verá estos campos en el modal de conexión de Datamo y los valores llegan a tu handler como:

credentials = {
  tenant_id: 'acme-corp',
  api_key:   'sk-abcdef...'
}

Ejemplo completo

import express from 'express'
import { DatamoPushClient } from '@datamo/sdk'
import { createDatamoRouter } from '@datamo/sdk/express'

// Tu modelo de DB (cualquier ORM)
interface DatamoConn { webhookUrl: string; webhookToken: string }

const app = express()

app.use('/integrations', createDatamoRouter({
  clientSecret: process.env.DATAMO_CLIENT_SECRET,
  getWebhookToken: async (workspaceId) => {
    const row = await db.datamo_connections.findOne({ workspaceId })
    return row?.webhookToken ?? null
  },

  onMetrics: async ({ workspaceId, credentials }) => {
    const stats = await myService.getStats(credentials.tenant_id, credentials.api_key)
    return [
      { key: 'usuarios_activos',  value: stats.activeUsers },
      { key: 'sesiones_hoy',      value: stats.sessionsToday },
      { key: 'mrr_usd',           value: stats.mrr, unit: 'USD' },
      { key: 'tasa_conversion',   value: stats.conversionRate, unit: '%' },
    ]
  },

  onConnect: async ({ workspaceId, webhookUrl, webhookToken }) => {
    await db.datamo_connections.upsert(
      { workspaceId },
      { webhookUrl, webhookToken },
    )
  },
}))

// Push en tiempo real: llamar cuando ocurra algo relevante
export async function pushMetricsToDatamo(workspaceId: string, metrics: { key: string; value: number }[]) {
  const conn = await db.datamo_connections.findOne({ workspaceId })
  if (!conn) return

  const client = new DatamoPushClient({ ...conn, clientSecret: process.env.DATAMO_CLIENT_SECRET })
  await client.send(metrics)
}

app.listen(process.env.PORT || 3000)

Seguridad

  • Las credenciales del usuario viajan cifradas con AES-256-GCM dentro de Datamo y solo se descifran en el momento del pull request, transmitiéndose en el header X-Datamo-Credentials (base64 JSON) exclusivamente a tu apiBaseUrl por HTTPS.
  • El webhookToken es un secreto de 64 caracteres hex (32 bytes) único por conexión. Trátalo como una contraseña.
  • Todos los requests de Datamo incluyen firma HMAC-SHA256 con timestamp para proteger contra replay attacks (ventana de 5 minutos).
  • POST /datamo/connect se verifica con tu clientSecret (opts.clientSecret, desde 0.3.0). Sin él, cualquiera que conozca un workspaceId podría reemplazar el webhookToken guardado y cortar la sincronización de ese cliente.
  • El push se firma con tu clientSecret (X-Datamo-Signature-V2, desde 0.4.0). La firma legacy con el webhookToken deja de aceptarse el 2026-11-29.

Tipos TypeScript exportados

import type {
  DatamoMetric,
  DatamoCredentials,
  DatamoMetricsContext,
  DatamoConnectPayload,
  DatamoRouterOptions,
  DatamoPushOptions,
  DatamoPushResult,
} from '@datamo/sdk'

Helpers de firma exportados: verifyDatamoSignature, isTimestampFresh, verifyConnectSignature, connectSigningPayload, signPushV2, pushSigningPayloadV2 y la constante LEGACY_PUSH_SIGNATURE_SUNSET ('2026-11-29').