Part of https://github.com/pnpm/tasks/issues/191. Wrong claims: - cli: drop the nonexistent `add-user --password` flag and document the terminal/stdin password read; `oci-gc --min-age` and `cache-gc --max-age` are the flags (the `-secs` forms are aliases); `--from` defaults to the `storage` key, not an internal field; `info` and `http` log the same access records; only global and server options have PNPR_* variables. - configuration: `packumentTtl` defaults to 5m; the minimal config now sets `auth.htpasswd.file`, and the auth section says accounts live in memory without it or a backend; grouped hosted registries default their org to `<ecosystem>~<name>`; the handshake is served for any surface; the pipeline surface also satisfies "something must be served"; add `package.update_maintainers` to the audit list. - tls-and-reverse-proxy: remove the invalid `publicUrl:` YAML key and the nonexistent `rate_limiting` setting. - backup-and-restore: `pnpr verify` hashes by default (`--check-hashes` does not exist, `--skip-hashes` skips only the hash comparison); `migrate-storage --verify` compares bytes. - webhooks: audit events do not cover publishes or token creation; the example uses a real action and the `actor` field. - cli: the `RUST_LOG` example keeps `pnpr::audit=info`. - install-acceleration: the `--public-url` host is exempt from the private-address refusal. - shared-side-effects-cache: `pnpm pipeline` publishes workspace-task artifacts. Missing facts: `routes.allowedPrivateNetworks`, the upstream `search` key and canonical `maxAge`, built-in `npm.jsr.io` route, relative path resolution, `log` defaults and `type`, `${VAR:-default}` substitution and the full secret-field list, `oci` defaults, `artifacts.quota` bounds, `migrate-storage` destination rules, `--osv false`, `--packument-ttl` default, S3 `forcePathStyle`/`allowHttp` defaults, grouped org layout, OIDC key table, container image section, `backend:` ignoring the htpasswd and token files. Duplicates and links: health probe bodies, reload procedure, and the resolver allowlist now link to their one home; broken `#secret` anchor fixed; links into the pnpm docs use absolute pnpm.io URLs. Stale history: drop "pnpr v1" and the per-paragraph "Since alpha.N" / "Since pnpm 12.x" prefixes, and the contributor-only extensibility text. Also corrects the `as_filter_directive` doc comment, which claimed `http` quiets the rest of the crate.
110 lines
5.0 KiB
Markdown
110 lines
5.0 KiB
Markdown
---
|
|
id: webhooks
|
|
title: Webhooks and event notifications
|
|
---
|
|
|
|
Organizations often need notifications when registry events occur, such as when a new package version is published, dist-tags change, or user permissions are modified.
|
|
|
|
This guide explains pnpr's event architecture and shows how to integrate external webhook dispatchers and notification pipelines.
|
|
|
|
## Architecture decision
|
|
|
|
pnpr deliberately does not maintain an in-process HTTP webhook dispatch queue.
|
|
|
|
Building an internal webhook queue inside a registry server introduces operational drawbacks:
|
|
|
|
- **Delivery guarantees**: An in-memory queue inside the server loses pending webhooks if an instance crashes or restarts during a deployment. Storing persistent queue state on local disk complicates running multiple stateless pnpr replicas behind a load balancer.
|
|
- **Process stability and head-of-line blocking**: Slow or unreachable webhook destination endpoints can cause queue buildup, memory pressure, and retry storms inside the registry process, potentially degrading core package download and publish throughput.
|
|
- **Separation of concerns**: Reliable event delivery, deduplication, retry policies, and fan-out are best handled by dedicated event streaming and log shipping systems.
|
|
|
|
Instead, pnpr provides durable, structured integration points designed to feed external webhook dispatchers and message brokers.
|
|
|
|
## Integration patterns
|
|
|
|
### 1. Structured audit logs
|
|
|
|
Administrative and destructive operations in pnpr emit a structured event under the `pnpr::audit` tracing target at `info` level. They cover account, team, rule, and SCIM changes, token revocation, unpublishes, packument and maintainer updates, dist-tag changes, and image deletions. Publishes and token creation are not audited. The full list of actions and their fields is in [Admin audit logs](configuration.md#admin-audit-logs).
|
|
|
|
When configured with `log.format: json`, pnpr outputs each audit event as a single-line JSON record on `stdout`:
|
|
|
|
```json
|
|
{
|
|
"timestamp": "2026-10-17T01:15:30.123456Z",
|
|
"level": "INFO",
|
|
"target": "pnpr::audit",
|
|
"fields": {
|
|
"action": "package.dist_tag_set",
|
|
"actor": "alice",
|
|
"package": "@mycompany/utils",
|
|
"tag": "latest",
|
|
"version": "1.2.0",
|
|
"registry": "local",
|
|
"message": "package dist-tag set"
|
|
},
|
|
"span": {
|
|
"name": "request",
|
|
"request_id": "c8a14b09-1a73-42e1-95be-726ff342f534",
|
|
"client_ip": "192.168.1.50"
|
|
}
|
|
}
|
|
```
|
|
|
|
Lightweight log shippers such as [Vector](https://vector.dev/), [Fluent Bit](https://fluentbit.io/), or [Fluentd](https://www.fluentd.org/) can filter for `target == "pnpr::audit"` and forward these events to an HTTP webhook endpoint, Slack, or a message broker (such as Kafka, RabbitMQ, or AWS SQS).
|
|
|
|
#### Example: Forwarding audit logs to a webhook with Vector
|
|
|
|
Here is a Vector configuration that extracts `pnpr::audit` events from pnpr's container logs and forwards them to a webhook URL:
|
|
|
|
```toml title="vector.toml"
|
|
[sources.pnpr_logs]
|
|
type = "docker_logs"
|
|
include_containers = ["pnpr"]
|
|
|
|
[transforms.parse_pnpr_logs]
|
|
type = "remap"
|
|
inputs = ["pnpr_logs"]
|
|
source = ". = parse_json!(.message)"
|
|
|
|
[transforms.filter_audit_events]
|
|
type = "filter"
|
|
inputs = ["parse_pnpr_logs"]
|
|
condition = '.target == "pnpr::audit"'
|
|
|
|
[sinks.webhook_dispatcher]
|
|
type = "http"
|
|
inputs = ["filter_audit_events"]
|
|
uri = "https://events.example.com/webhooks/registry"
|
|
method = "post"
|
|
encoding.codec = "json"
|
|
|
|
[sinks.webhook_dispatcher.request.headers]
|
|
Authorization = "Bearer ${WEBHOOK_SECRET}"
|
|
Content-Type = "application/json"
|
|
```
|
|
|
|
### 2. Object storage bucket notifications
|
|
|
|
When pnpr is configured with an S3-compatible backend (AWS S3, Cloudflare R2, MinIO), you can configure bucket notifications directly on your object storage bucket.
|
|
|
|
Because package tarballs are stored as immutable blobs and packuments are written as JSON documents under the bucket prefix, storage notifications provide durable, real-time event triggers:
|
|
|
|
- **AWS S3**: Configure an EventBridge or SNS/SQS notification for `s3:ObjectCreated:*` events matching the prefix of hosted packages. An AWS Lambda function or worker consumes the event and dispatches your webhook.
|
|
- **MinIO**: Use MinIO bucket event notifications to dispatch events directly to an HTTP webhook endpoint.
|
|
- **Cloudflare R2**: Use R2 Event Notifications connected to a Cloudflare Worker or queue to invoke downstream webhooks.
|
|
|
|
### 3. CI/CD post-publish steps
|
|
|
|
For publishing workflows orchestrated by continuous integration (such as GitHub Actions or GitLab CI), invoking notification webhooks immediately after `pnpm publish` provides immediate, synchronous confirmation without requiring server-side webhooks:
|
|
|
|
```yaml title=".github/workflows/release.yml"
|
|
- name: Publish to pnpr
|
|
run: pnpm publish --registry https://registry.example.com/npm/
|
|
|
|
- name: Dispatch notification webhook
|
|
if: success()
|
|
run: |
|
|
curl -X POST https://events.example.com/webhooks/published \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"package": "${{ github.repository }}", "ref": "${{ github.ref_name }}"}'
|
|
```
|