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

@scrape-admin/webhook

v0.2.0

Published

Shared helpers for sending and receiving Scrape Admin webhooks:

Readme

@scrape-admin/webhook

Shared helpers for sending and receiving Scrape Admin webhooks:

  • Sending — build the request headers (including token/auth handling) and deliver a Webhook or WebhookDestination (see @scrape-admin/schema).
  • Receiving — verify the bearer token and validate the ResultBag body Scrape Admin POSTs to destination webhooks, plus a ready-made h3 handler at @scrape-admin/webhook/h3.

Installation

npm install @scrape-admin/webhook

Sending webhooks

import { defineWebhook, deliverWebhook } from '@scrape-admin/webhook'

const webhook = defineWebhook({
  url: 'https://example.com/hooks/scrape-admin',
  method: 'POST',
  token: 'secret',
  tokenType: 'bearer',
})

const response = await deliverWebhook(webhook)

Receiving destination webhooks

When a collection entry runs, Scrape Admin POSTs the run's ResultBag as the bare JSON body (no envelope) to each of its destinations, with Authorization: Bearer <token> when the destination has a token.

With h3 (Nuxt / Nitro)

h3 is an optional peer dependency (^1.15.0); install it if you use this subpath.

// server/api/scrape-admin/[targetId].post.ts
import { defineScrapeAdminWebhookHandler } from '@scrape-admin/webhook/h3'

export default defineScrapeAdminWebhookHandler({
  // Look up the target and the secrets it accepts (e.g. current + previous during rotation).
  // Return null for an unknown target.
  resolveSecrets: async (event) => {
    const target = await findTarget(getRouterParam(event, 'targetId'))
    return target ? { secrets: [target.secret, target.previousSecret], context: target } : null
  },
  // Called with a verified, validated result. Its return value is the response.
  onResult: async (event, result, target, { bodyHash }) => {
    await saveResult(target, result, bodyHash)
    return { ok: true }
  },
  maxBodyBytes: 262_144, // default (256 KiB)
})

| Status | When | | --- | --- | | 404 | resolveSecrets returned null. | | 401 | The Authorization header is missing or matches none of the secrets. | | 413 | The body (or its declared Content-Length) exceeds maxBodyBytes. | | 422 | The body is not JSON, or not a valid ResultBag (zod issues in data.issues). |

onResult also receives { rawBody, bodyHash }, where bodyHash is the hex SHA-256 of the raw body, which is useful for idempotency and auditing.

Framework-agnostic

The building blocks are exported from the package root and only use WebCrypto (no node:crypto), so they run on Node, Cloudflare Workers, Deno and Bun:

import { resultBagSchema, verifyBearerToken } from '@scrape-admin/webhook'

export default {
  async fetch(request: Request, env: { CURRENT_SECRET: string, PREVIOUS_SECRET?: string }) {
    if (!(await verifyBearerToken(request.headers.get('authorization'), [env.CURRENT_SECRET, env.PREVIOUS_SECRET])))
      return new Response('Unauthorized', { status: 401 })

    const parsed = resultBagSchema.safeParse(await request.json())
    if (!parsed.success) return Response.json({ issues: parsed.error.issues }, { status: 422 })

    const { url, attributes, statusCode, error } = parsed.data
    // ...
    return new Response(null, { status: 204 })
  },
}

| Export | Description | | --- | --- | | verifyBearerToken(header, secrets) | Constant-time check of an Authorization: Bearer header against one or more secrets. Empty secrets are ignored. | | extractBearerToken(header) | Returns the token from a Bearer <token> header value, or undefined. | | timingSafeEqual(a, b) | Constant-time Uint8Array comparison. | | sha256Hex(input) | Hex SHA-256 of a UTF-8 string. | | resultBagSchema / ResultBag | Lenient zod schema and type for the result body. |

resultBagSchema mirrors ResultBag from @scrape-admin/profile-runner: url is required, variables and attributes default to {}, redirect, statusCode and error are optional (statusCode/error also accept null), and unknown keys are kept so newer Scrape Admin versions don't break parsing.

See ./src/index.ts and ./src/h3.ts for the full exported surface.