@lastshotlabs/slingshot-webhooks
npm install @lastshotlabs/slingshot-webhooks
Functions
Section titled “Functions”createMemoryWebhookAdapter
Section titled “createMemoryWebhookAdapter”Creates an ephemeral in-memory webhook adapter suitable for tests and local development.
function createMemoryWebhookAdapter(): MemoryWebhookAdapterSource: packages/slingshot-webhooks/src/adapters/memory.ts
createSecretCipher
Section titled “createSecretCipher”Build a SecretCipher. When keyB64 is undefined, the cipher acts as
a no-op passthrough; the plugin warns at boot when this happens in
production-like environments.
function createSecretCipher(keyB64: string | undefined | null): SecretCipherSource: packages/slingshot-webhooks/src/lib/secretCipher.ts
createSlidingWindowRateLimiter
Section titled “createSlidingWindowRateLimiter”Create an in-memory sliding-window rate limiter.
Tracks request timestamps per key in a Map. On each check() call,
timestamps older than windowMs are pruned. If the remaining entry count
is at or above maxRequests, the request is denied.
function createSlidingWindowRateLimiter(options: Partial<SlidingWindowRateLimiterOptions> = {},): RateLimiterSource: packages/slingshot-webhooks/src/lib/rateLimit.ts
createWebhookMemoryQueue
Section titled “createWebhookMemoryQueue”Creates an in-process, non-durable webhook delivery queue for development and testing.
Jobs are processed inline — no external dependencies required. All pending jobs are lost
on process restart. Use createBullMQWebhookQueue for durable, Redis-backed delivery.
When config.dlqStoragePath is provided, dead-lettered jobs are also persisted to a
JSON-lines file. Use replayWebhookDlq to re-process them after restart.
function createWebhookMemoryQueue(config?: MemoryQueueConfig): WebhookQueueSource: packages/slingshot-webhooks/src/queues/memory.ts
createWebhooksPackage
Section titled “createWebhooksPackage”Create the webhooks package using the definePackage authoring path.
Mounts the WebhookEndpoint and WebhookDelivery entities (each with manual
adapter wiring that wraps the standard adapter in subscription
normalization, secret encryption, and the transition state machine),
starts the queue lifecycle, supplies the bespoke /endpoints/:id/test
and /admin/deliveries/:id/replay routes plus the inbound webhook
receiver, and publishes the unified WebhooksAdapterCap capability
once the runtime is ready.
When config.adapter is supplied, the package skips entity wiring and
uses the caller-provided adapter directly — the entity modules and routes
remain unmounted in that mode.
function createWebhooksPackage(rawConfig: WebhookPluginConfig): SlingshotPackageDefinitionSource: packages/slingshot-webhooks/src/plugin.ts
replayWebhookDlq
Section titled “replayWebhookDlq”Re-process every dead-lettered webhook job stored in the given JSON-lines
file by passing each to the provided enqueueFn callback. Jobs that are
successfully enqueued are removed from the file; jobs whose callback
rejects are retained.
async function replayWebhookDlq(storagePath: string, enqueueFn: (job: WebhookJob) => Promise<void>,): Promise<Source: packages/slingshot-webhooks/src/queues/memory.ts
safeParseInboundBody
Section titled “safeParseInboundBody”Safely parse a raw inbound webhook body string as JSON.
Returns { ok: true, payload } on success, or { ok: false, reason } for any
non-string input, empty body, or JSON.parse failure. Implementers should map a
failed result to { verified: false, reason } in their InboundProvider.verify()
return value so the route can respond with HTTP 400.
function safeParseInboundBody(rawBody: string): SafeParseInboundBodyResultSource: packages/slingshot-webhooks/src/lib/inbound.ts
signPayload
Section titled “signPayload”Signs a webhook payload using HMAC-SHA256 and returns a Stripe-style signature header value.
Signature format: t=<unix_timestamp>,v1=<hex_hmac> where the signed data is <ts>.<body>.
This format is intentionally compatible with Stripe’s webhook signature scheme.
async function signPayload(secret: string, body: string, timestamp?: number,): Promise<string>Source: packages/slingshot-webhooks/src/lib/signing.ts
verifySignature
Section titled “verifySignature”Verifies a webhook signature header produced by signPayload.
Parses the t=... timestamp and v1=... hex HMAC from the header, recomputes the
expected HMAC, and uses a constant-time comparison via crypto.subtle.verify.
Rejects signatures where the timestamp differs from now by more than toleranceSeconds
to prevent replay attacks.
async function verifySignature(secret: string, body: string, header: string, toleranceSeconds = 300,): Promise<boolean>Source: packages/slingshot-webhooks/src/lib/signing.ts
webhookPluginConfigSchema
Section titled “webhookPluginConfigSchema”Zod schema for validating WebhookPluginConfig.
Config Fields
Section titled “Config Fields”| Field | Description |
|---|---|
| `/** |
- Advanced outbound dispatch overrides. Production traffic should normally
- use the default safeFetch transport; tests can inject
fetchImpland a - deterministic resolver without weakening SSRF validation.
/
dispatch
| Optional host resolver override for outbound delivery validation. | |/* Delivery queue implementation. Defaults to the in-process memory queue. / queue| In-memory webhook delivery queue (development only). | |/* Mount path for webhook routes. Default| URL path prefix for webhook routes. Omit to use '/webhooks'. | |/** Role required for webhook management routes. Default| Role required for webhook management routes. Omit to use 'admin'. | |each inbound provider (e.g. ‘stripe’, ‘github’) is rate-limited - independently using an in-memory sliding window counter. Requests that exceed
- the limit receive HTTP 429 with
Retry-AfterandX-RateLimit-*headers. - Provide a custom
RateLimiterinstance for distributed deployments (e.g. Redis - sliding window) or use the shorthand object form for the built-in per-process
- limiter.
- Omit entirely to disable inbound rate limiting (not recommended in production).
*/
inboundRateLimit
| Custom RateLimiter instance (e.g. Redis-backed). | |in bytes. Default` | Maximum body size (bytes) accepted on inbound webhook routes. Defaults to 1 MiB. |
Source: packages/slingshot-webhooks/src/types/config.ts
Constants
Section titled “Constants”WEBHOOK_ROUTES
Section titled “WEBHOOK_ROUTES”Named constants for the two route groups mounted by the webhook plugin.
Pass values to WebhookPluginConfig.disableRoutes to skip mounting specific route groups.
Source: packages/slingshot-webhooks/src/routes/index.ts
Webhooks
Section titled “Webhooks”Provider-owned package contract for slingshot-webhooks.
Source: packages/slingshot-webhooks/src/public.ts
WebhooksAdapterCap
Section titled “WebhooksAdapterCap”Capability handle for the unified webhook adapter.
Cross-package consumers resolve it through ctx.capabilities.require(WebhooksAdapterCap)
to send and manage outbound webhook deliveries.
Source: packages/slingshot-webhooks/src/public.ts
Classes
Section titled “Classes”WebhookDeliveryError
Section titled “WebhookDeliveryError”Thrown by the webhook dispatcher when an HTTP delivery attempt fails.
The retryable flag controls queue behaviour: non-retryable errors (e.g. 4xx client
errors except 429) are dead-lettered immediately; retryable errors (e.g. network
timeout, 5xx, 429) are re-queued up to config.queueConfig.maxAttempts.
Source: packages/slingshot-webhooks/src/types/queue.ts
WebhookSecretDecryptError
Section titled “WebhookSecretDecryptError”Thrown when the secret cipher fails to decrypt a stored webhook endpoint secret. Falling back to the ciphertext (or any plaintext) would either permanently sign deliveries with the wrong key or, worse, leak the encrypted blob to the receiver — so the runtime fails closed and the caller is expected to skip the affected endpoint.
The error message intentionally never embeds the cipher value so it is safe to log.
Source: packages/slingshot-webhooks/src/types/queue.ts
Interfaces
Section titled “Interfaces”InboundProvider
Section titled “InboundProvider”Interface for verifying and processing inbound webhook payloads from external services.
Each provider handles one external service (e.g. Stripe, GitHub). On receipt of a
POST /webhooks/inbound/<provider> request, the plugin calls verify(). If verification
passes, the payload is re-emitted as webhook:inbound.<provider> on the bus.
Implementers MUST handle malformed JSON. The rawBody is attacker-controlled, so a
naive JSON.parse(rawBody) will throw on bad input and surface as an unhelpful 500.
Use the safeParseInboundBody helper exported from this package, or wrap JSON.parse
in your own try/catch and return { verified: false, reason } on failure.
Source: packages/slingshot-webhooks/src/types/inbound.ts
MemoryQueueConfig
Section titled “MemoryQueueConfig”Optional configuration for createWebhookMemoryQueue.
Remarks: All fields are optional — the queue uses safe defaults when omitted.
Source: packages/slingshot-webhooks/src/queues/memory.ts
MemoryWebhookAdapter
Section titled “MemoryWebhookAdapter”In-memory WebhookAdapter with synchronous endpoint management helpers for testing.
Source: packages/slingshot-webhooks/src/adapters/memory.ts
RateLimiter
Section titled “RateLimiter”Contract for rate limiter implementations used by the inbound webhook router.
Source: packages/slingshot-webhooks/src/lib/rateLimit.ts
RateLimitResult
Section titled “RateLimitResult”Result returned by RateLimiter.check.
Source: packages/slingshot-webhooks/src/lib/rateLimit.ts
SecretCipher
Section titled “SecretCipher”AES-256-GCM cipher for at-rest webhook endpoint secrets.
Plaintexts are written as enc:v1:<iv-b64>:<ciphertext-b64>:<tag-b64>. When
the cipher is created with no key (or with an explicit null key), it acts
as a no-op passthrough — used in test fixtures and during the migration
window before operators provision an encryption key.
Legacy plaintext values that pre-date encryption are detected by the missing
enc:v1: prefix and returned unchanged from decrypt. Operators
should rotate those rows by writing the same value back through the update
path, which re-encrypts on write.
Source: packages/slingshot-webhooks/src/lib/secretCipher.ts
SecretEncryptor
Section titled “SecretEncryptor”Pluggable encryptor for at-rest webhook endpoint secrets.
Apps can supply a custom implementation backed by KMS, HashiCorp Vault, or
any other key manager. The default implementation built from
createSecretCipher performs local AES-256-GCM with a base64 key, but
the framework only depends on this interface so an external provider can be
dropped in without touching the storage path.
Implementations should:
- Return strings that round-trip through
decryptunchanged. - Tolerate values produced before encryption was enabled (the local implementation passes such values through and operators rotate by re-saving).
encrypt/decrypt may be sync or async; the runtime awaits both.
Source: packages/slingshot-webhooks/src/lib/secretCipher.ts
SlidingWindowRateLimiterOptions
Section titled “SlidingWindowRateLimiterOptions”Options for createSlidingWindowRateLimiter.
Source: packages/slingshot-webhooks/src/lib/rateLimit.ts
WebhookAdapter
Section titled “WebhookAdapter”Runtime persistence contract used by webhook orchestration.
Source: packages/slingshot-webhooks/src/types/adapter.ts
WebhookAttempt
Section titled “WebhookAttempt”Metadata for a single delivery attempt.
Source: packages/slingshot-webhooks/src/types/models.ts
WebhookDelivery
Section titled “WebhookDelivery”Persisted outbound delivery record.
The version field carries the optimistic concurrency token used by the
dispatcher to coordinate concurrent updates against the same delivery row
(P-WEBHOOKS-6). Adapters bump it on every successful update; callers pass
the value they read alongside their write so a stale write becomes a
conflict instead of clobbering newer state.
Source: packages/slingshot-webhooks/src/types/models.ts
WebhookEndpoint
Section titled “WebhookEndpoint”Persisted outbound webhook endpoint.
Source: packages/slingshot-webhooks/src/types/models.ts
WebhookEndpointSubscription
Section titled “WebhookEndpointSubscription”A single event subscription attached to a webhook endpoint.
Source: packages/slingshot-webhooks/src/types/models.ts
WebhookJob
Section titled “WebhookJob”A single webhook delivery job tracked by the queue.
Contains all the data needed to execute one HTTP delivery attempt without additional
database lookups. Exposed to queue processors and onDeadLetter callbacks.
Source: packages/slingshot-webhooks/src/types/queue.ts
WebhookQueue
Section titled “WebhookQueue”Interface that every webhook queue implementation must satisfy.
Extends QueueLifecycle with webhook-specific enqueue and start(processor).
The in-process MemoryQueue and BullMQ-backed queue both implement this interface.
Remarks: Implement this interface to integrate a custom queue backend (e.g. SQS, RabbitMQ).
Source: packages/slingshot-webhooks/src/types/queue.ts
WebhookSubscriber
Section titled “WebhookSubscriber”Identity of the entity that owns or receives a webhook delivery.
Source: packages/slingshot-webhooks/src/types/models.ts
DeliveryStatus
Section titled “DeliveryStatus”Lifecycle status of a webhook delivery.
Source: packages/slingshot-webhooks/src/types/models.ts
SafeParseInboundBodyResult
Section titled “SafeParseInboundBodyResult”Result of safeParseInboundBody.
Source: packages/slingshot-webhooks/src/lib/inbound.ts
WebhookEndpointSubscriptionInput
Section titled “WebhookEndpointSubscriptionInput”Input union for subscribing an endpoint to a specific event key or a glob pattern.
Source: packages/slingshot-webhooks/src/types/models.ts
WebhookOwnerType
Section titled “WebhookOwnerType”Discriminator indicating who owns a webhook endpoint or subscription.
Source: packages/slingshot-webhooks/src/types/models.ts
WebhookPluginConfig
Section titled “WebhookPluginConfig”Configuration object accepted by createWebhooksPackage.
Source: packages/slingshot-webhooks/src/types/config.ts
WebhookRoute
Section titled “WebhookRoute”Union of valid route group names that can be passed to WebhookPluginConfig.disableRoutes.
Source: packages/slingshot-webhooks/src/routes/index.ts
WebhooksPluginConfig
Section titled “WebhooksPluginConfig”Canonical configuration name matching the plural package name.
Source: packages/slingshot-webhooks/src/types/config.ts
WebhookSubscriptionExposure
Section titled “WebhookSubscriptionExposure”Visibility scope that determines which callers may manage a subscription.
Source: packages/slingshot-webhooks/src/types/models.ts