.md

sl.webhooks

Create webhooks, read their deliveries and replay history. The same data the Webhooks API serves over HTTP.

@secondlayer/sdk@15.5.015 methodsfrom source with openpkgWebhooks guideWebhooks API
list(){ data: WebhookSummary[] }List your webhooks.
get(id)WebhookDetailGet one webhook's full configuration.
create(input)CreateWebhookResponseCreate a webhook that POSTs chain events to your URL.
update(id, patch)WebhookDetailChange a webhook's name, URL, format, auth, or delivery limits.
pause(id)WebhookDetailStop deliveries without deleting the webhook.
resume(id)WebhookDetailStart delivering again after a pause.
delete(id){ ok: true }Delete a webhook.
rotateSecret(id)RotateSecretResponseIssue a new signing secret.
test(id)WebhookTestResultSend a one-off test delivery to a webhook's URL.
deliveries(id, window){ total: number; data: DeliveryRow[…The last 100 delivery attempts, newest first.
activity(id, opts)WebhookActivityGet a webhook's hourly delivery counts for the last 7 days.
delivery(id, deliveryId)WebhookDeliveryDetailGet one delivery attempt with its payload and response.
replay(id, range)ReplayResultRe-deliver historical events over an existing webhook.
dead(id){ data: DeadRow[] }List the events a webhook gave up delivering.
requeue(id, outboxId){ ok: true }Push one dead-lettered event back onto the delivery queue.

List your webhooks.

Each summary has the status, format, URL, last delivery and last success, and the circuit breaker state (circuitFailures, circuitOpenedAt).

sl.webhooks.list(): Promise<{ data: WebhookSummary[] }>

Get one webhook's full configuration.

Adds the triggers, the receiver's auth (redacted to header names and whether a secret is set), the retry and timeout limits, lastError, and a warning when this instance's trigger evaluator is not running.

sl.webhooks.get(id: string): Promise<WebhookDetail>

Parameters

hover a row to find it in the code

Required

idrequiredstring
Returns Promise<WebhookDetail>
triggers
Chain-trigger filters.
auth
WebhookAuth
maxRetries
number
timeoutMs
number
concurrency
number
lastError
string | null
receiverCertExpiresAt
When the receiver's TLS certificate expires, from the periodic probe. Null for http receivers, refused or unreachable hosts, and webhooks not probed yet.
warning
Set when this instance's chain-trigger evaluator isn't running (SUBGRAPH_SOURCE != streams-index) — the webhook will never fire until that's fixed. Null otherwise. Surfaced on create (so it's seen immediately) and on every read (so secondlayer webhooks doctor reports it for a webhook created before the mismatch, or on an instance whose config changed since).
id
string
name
string
status
WebhookStatus
kind
WebhookKind
format
WebhookFormat
url
string
lastDeliveryAt
string | null
lastSuccessAt
string | null
circuitOpenedAt
When the circuit breaker paused this webhook (20 straight failures); cleared on resume. Present on the list summary (not detail-only) so a numbers-only insight can be shown per row without a per-row fetch.
circuitFailures
Consecutive failed deliveries (reset to 0 on a success). On the summary so the list can show a failing receiver per row without a per-row fetch.
managedBy
Owning product, derived from the name prefix (MANAGED_NAME_PREFIXES). When non-null the API refuses to rename the webhook.
createdAt
string
updatedAt
string

Create a webhook that POSTs chain events to your URL.

Takes a name, a url, and triggers built with trigger.*; each trigger is validated before anything is provisioned, and an invalid one is a 400. The signing secret comes back once in signingSecret, so store it. A webhook starts at the chain tip and never backfills: use replay for history. Hosted accounts cap maxRetries at 7 and timeoutMs at 30000.

sl.webhooks.create(input: CreateWebhookRequest): Promise<CreateWebhookResponse>
import { SecondLayer, trigger } from "@secondlayer/sdk";
const sl = new SecondLayer({
apiKey: process.env.SECONDLAYER_API_KEY,
});
const { webhook, signingSecret } = await sl.webhooks.create({
name: "sbtc-mints",
url: "https://my-app.com/webhook",
triggers: [
trigger.ftMint({
assetIdentifier:
"SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token::sbtc-token",
}),
],
});
console.log(`created ${webhook.id} (${webhook.status})`);
console.log(`WEBHOOK_SECRET=${signingSecret}`);
^Returned once. Store it: verifyWebhookSignature needs it to check each delivery.

Parameters

hover a row to find it in the code

Required

namerequiredstring
triggersrequiredChainTrigger[]
urlrequiredstring

Options

formatWebhookFormat
authConfigRecord<string, unknown>
maxRetriesnumber
timeoutMsnumber
concurrencynumber
Returns Promise<CreateWebhookResponse>
webhook
WebhookDetail
signingSecret
Plaintext signing secret — surfaced ONCE. Store it server-side.

Change a webhook's name, URL, format, auth, or delivery limits.

Send only the fields to change: name, url, format, authConfig, maxRetries, timeoutMs, concurrency. Triggers are not part of an update. Resolves to the updated webhook.

sl.webhooks.update(id: string, patch: UpdateWebhookRequest): Promise<WebhookDetail>

Parameters

hover a row to find it in the code

Required

idrequiredstring

Options

patch.namestring
patch.urlstring
patch.formatWebhookFormat
patch.authConfigRecord<string, unknown>
patch.maxRetriesnumber
patch.timeoutMsnumber
patch.concurrencynumber
Returns Promise<WebhookDetail>
triggers
Chain-trigger filters.
auth
WebhookAuth
maxRetries
number
timeoutMs
number
concurrency
number
lastError
string | null
receiverCertExpiresAt
When the receiver's TLS certificate expires, from the periodic probe. Null for http receivers, refused or unreachable hosts, and webhooks not probed yet.
warning
Set when this instance's chain-trigger evaluator isn't running (SUBGRAPH_SOURCE != streams-index) — the webhook will never fire until that's fixed. Null otherwise. Surfaced on create (so it's seen immediately) and on every read (so secondlayer webhooks doctor reports it for a webhook created before the mismatch, or on an instance whose config changed since).
id
string
name
string
status
WebhookStatus
kind
WebhookKind
format
WebhookFormat
url
string
lastDeliveryAt
string | null
lastSuccessAt
string | null
circuitOpenedAt
When the circuit breaker paused this webhook (20 straight failures); cleared on resume. Present on the list summary (not detail-only) so a numbers-only insight can be shown per row without a per-row fetch.
circuitFailures
Consecutive failed deliveries (reset to 0 on a success). On the summary so the list can show a failing receiver per row without a per-row fetch.
managedBy
Owning product, derived from the name prefix (MANAGED_NAME_PREFIXES). When non-null the API refuses to rename the webhook.
createdAt
string
updatedAt
string

Stop deliveries without deleting the webhook.

Resolves to the updated webhook. resume turns it back on.

sl.webhooks.pause(id: string): Promise<WebhookDetail>

Parameters

hover a row to find it in the code

Required

idrequiredstring
Returns Promise<WebhookDetail>
triggers
Chain-trigger filters.
auth
WebhookAuth
maxRetries
number
timeoutMs
number
concurrency
number
lastError
string | null
receiverCertExpiresAt
When the receiver's TLS certificate expires, from the periodic probe. Null for http receivers, refused or unreachable hosts, and webhooks not probed yet.
warning
Set when this instance's chain-trigger evaluator isn't running (SUBGRAPH_SOURCE != streams-index) — the webhook will never fire until that's fixed. Null otherwise. Surfaced on create (so it's seen immediately) and on every read (so secondlayer webhooks doctor reports it for a webhook created before the mismatch, or on an instance whose config changed since).
id
string
name
string
status
WebhookStatus
kind
WebhookKind
format
WebhookFormat
url
string
lastDeliveryAt
string | null
lastSuccessAt
string | null
circuitOpenedAt
When the circuit breaker paused this webhook (20 straight failures); cleared on resume. Present on the list summary (not detail-only) so a numbers-only insight can be shown per row without a per-row fetch.
circuitFailures
Consecutive failed deliveries (reset to 0 on a success). On the summary so the list can show a failing receiver per row without a per-row fetch.
managedBy
Owning product, derived from the name prefix (MANAGED_NAME_PREFIXES). When non-null the API refuses to rename the webhook.
createdAt
string
updatedAt
string

Start delivering again after a pause.

Also clears a circuit-breaker pause, which happens after 20 straight failed deliveries. Resolves to the updated webhook.

sl.webhooks.resume(id: string): Promise<WebhookDetail>

Parameters

hover a row to find it in the code

Required

idrequiredstring
Returns Promise<WebhookDetail>
triggers
Chain-trigger filters.
auth
WebhookAuth
maxRetries
number
timeoutMs
number
concurrency
number
lastError
string | null
receiverCertExpiresAt
When the receiver's TLS certificate expires, from the periodic probe. Null for http receivers, refused or unreachable hosts, and webhooks not probed yet.
warning
Set when this instance's chain-trigger evaluator isn't running (SUBGRAPH_SOURCE != streams-index) — the webhook will never fire until that's fixed. Null otherwise. Surfaced on create (so it's seen immediately) and on every read (so secondlayer webhooks doctor reports it for a webhook created before the mismatch, or on an instance whose config changed since).
id
string
name
string
status
WebhookStatus
kind
WebhookKind
format
WebhookFormat
url
string
lastDeliveryAt
string | null
lastSuccessAt
string | null
circuitOpenedAt
When the circuit breaker paused this webhook (20 straight failures); cleared on resume. Present on the list summary (not detail-only) so a numbers-only insight can be shown per row without a per-row fetch.
circuitFailures
Consecutive failed deliveries (reset to 0 on a success). On the summary so the list can show a failing receiver per row without a per-row fetch.
managedBy
Owning product, derived from the name prefix (MANAGED_NAME_PREFIXES). When non-null the API refuses to rename the webhook.
createdAt
string
updatedAt
string

Delete a webhook.

Resolves to { ok: true }. Throws a 404 when the webhook does not exist.

sl.webhooks.delete(id: string): Promise<{ ok: true }>

Parameters

hover a row to find it in the code

Required

idrequiredstring

Issue a new signing secret.

The new secret comes back once in signingSecret. Update the receiver that calls verifyWebhookSignature with it: deliveries are signed with the new secret from now on.

sl.webhooks.rotateSecret(id: string): Promise<RotateSecretResponse>

Parameters

hover a row to find it in the code

Required

idrequiredstring
Returns Promise<RotateSecretResponse>
webhook
WebhookDetail
signingSecret
string

Send a one-off test delivery to a webhook's URL.

Built for the webhook's format and SSRF-guarded. Logged as a delivery row, visible via deliveries.

sl.webhooks.test(id: string): Promise<WebhookTestResult>

Parameters

hover a row to find it in the code

Required

idrequiredstring
Returns Promise<WebhookTestResult>
ok
boolean
statusCode
number | null
error
string | null
durationMs
number
deliveryId
string

The last 100 delivery attempts, newest first. The server caps the result; there is nothing to page. Pass window (ISO timestamps, at most 31 days wide) to read only the attempts dispatched in [from, to), for example one hour of the heat strip; total then counts that window.

sl.webhooks.deliveries(
id: string,
window?: { from: string; to: string },
): Promise<{ total: number; data: DeliveryRow[] }>

Parameters

hover a row to find it in the code

Required

idrequiredstring
window.fromrequiredstring
window.torequiredstring

sl.webhooks.activity(id, opts)

REST GET /api/webhooks/{id}/activitysource

Get a webhook's hourly delivery counts for the last 7 days.

Each hour has delivered, waiting, and gave-up counts, plus the current queue depth, failed attempts per hour, and the usual band. Pass { days: 30 } to also get 30 daily totals. This is the events chart on the detail page.

sl.webhooks.activity(id: string, opts?: { days?: 30 }): Promise<WebhookActivity>

Parameters

hover a row to find it in the code

Required

idrequiredstring

Options

opts.days30
Returns Promise<WebhookActivity>
hours
168 hours, oldest first.
waiting
Pending rows right now, no time bound.
nextAttemptAt
string | null
lastSuccessAt
string | null
byEventType
7-day counts per event_type (same 168-hour window as hours), e.g. chain.stx_transfer.apply — the config card's per-trigger volume.
usualByHourOfDay
Typical delivered attempts for each UTC hour of day (p10 and p90 of the last 28 days, from webhook_deliveries). Null at low volume, where "usual" is not meaningful.
days
Present with ?days=30: 30 UTC days, oldest first, from webhook_deliveries (2xx is delivered, everything else failed).

sl.webhooks.delivery(id, deliveryId)

REST GET /api/webhooks/{id}/deliveries/{deliveryId}source

Get one delivery attempt with its payload and response.

Includes the outbox context (payload, response headers). This is the delivery card on the detail page.

sl.webhooks.delivery(id: string, deliveryId: string): Promise<WebhookDeliveryDetail>

Parameters

hover a row to find it in the code

Required

idrequiredstring
deliveryIdrequiredstring
Returns Promise<WebhookDeliveryDetail>
id
string
attempt
number
statusCode
number | null
durationMs
number | null
dispatchedAt
string
errorMessage
string | null
responseBody
string | null
responseHeaders
Record<string, string> | null
outboxId
string | null
outboxStatus
The outbox row's own status — null once it's gone (compacted after delivery, or never had one, e.g. a test delivery). Only "dead" means a resend is possible: it's the one status POST /:id/dead/:outboxId/ requeue accepts.
eventType
string | null
txId
string | null
blockHeight
number | null
blockTime
string | null
eventIndex
The row's position within its block/tx, from the outbox's row_pk (event_index for a chain-trigger row). Null when row_pk carries none — a settlement or reorg outbox row, or no outbox row at all.
payload
unknown

Re-deliver historical events over an existing webhook.

Pass fromBlock and toBlock; the range is capped at 100,000 blocks. A replay is historical only and never moves the live cursor. It is idempotent: the same range twice is a no-op, so pass any string as force to re-deliver it. Replays carry is_replay and use the standard chain.{type}.apply envelope. Resolves to replayId, enqueuedCount, and scannedCount. A second replay while one is running is a 409. sbtc_withdrawal_swept_confirmed is forward-only and never replayed. Hosted accounts get 5 replays an hour.

sl.webhooks.replay(
id: string,
range: { fromBlock: number; toBlock: number; force?: string },
): Promise<ReplayResult>
import { SecondLayer } from "@secondlayer/sdk";
const id = process.argv[2];
if (!id) throw new Error("usage: bun replay.ts <webhook id>");
const sl = new SecondLayer({
apiKey: process.env.SECONDLAYER_API_KEY,
});
const range = { fromBlock: 8_000_000, toBlock: 8_050_000 };
const first = await sl.webhooks.replay(id, range);
console.log(first.replayId, first.scannedCount, first.enqueuedCount);
const again = await sl.webhooks.replay(id, { ...range, force: "retry-1" });
^The same range twice is a no-op. Pass any string as `force` to re-deliver it.
console.log(again.replayId);

Parameters

hover a row to find it in the code

Required

idrequiredstring
range.fromBlockrequirednumber
range.toBlockrequirednumber

Options

range.forcestring
Returns Promise<ReplayResult>
replayId
string
enqueuedCount
number
scannedCount
number

List the events a webhook gave up delivering.

Returns up to 100 dead-lettered events, newest failure first. Each row's id is what requeue takes.

sl.webhooks.dead(id: string): Promise<{ data: DeadRow[] }>

Parameters

hover a row to find it in the code

Required

idrequiredstring

sl.webhooks.requeue(id, outboxId)

REST POST /api/webhooks/{id}/dead/{outboxId}/requeuesource

Push one dead-lettered event back onto the delivery queue. outboxId is the id of a row from dead.

sl.webhooks.requeue(id: string, outboxId: string): Promise<{ ok: true }>

Parameters

hover a row to find it in the code

Required

idrequiredstring
outboxIdrequiredstring
ChainTrigger
JSON mirror of the subgraph runtime's SubgraphFilter union.
ChainTriggerAmount
Non-negative integer amount over JSON (string for uint128 safety, or number).
CreateWebhookRequest
The body for creating a webhook: name, triggers, URL, delivery options.
CreateWebhookResponse
The new webhook and its signing secret, shown once.
DeadRow
A delivery that ran out of retries.
DeliveryRow
One delivery attempt: status code, error, duration, and response.
ReplayResult
How many events a replay scanned and enqueued.
RotateSecretResponse
The webhook and its new signing secret.
TraitScoped
trait
UpdateWebhookRequest
The fields of a webhook you can change.
WebhookActivity
A week of hourly delivery counts and what is waiting now.
WebhookActivityDay
One UTC day of delivery attempts (GET /:id/activity?days=30).
WebhookActivityHour
One hour of webhook_outbox activity (GET /:id/activity), zero-filled so the events chart always has 168 bars.
WebhookAuth
What a webhook's receiver credentials look like once redacted for a response — never the bearer token, basic-auth string, or header values themselves, only enough to show what's set.
WebhookDeliveryDetail
One delivery attempt with its outbox context.
WebhookDetail
A webhook with its triggers, auth, retry, and timeout settings.
WebhookSummary
A webhook's name, status, format, and URL.
WebhookTestResult
Result of a one-off test delivery (POST /:id/test).
Edit on GitHub