Webhook

Webhooks and events: everything that happens, delivered to your server, signed.

hook = noclick.Webhook.from_name("https://api.yourco.test/noclick/events", create_if_missing=True,
                                 events=["operation.*", "human.answered"])
secret = hook.reveal_secret()   # needs the webhooks:reveal scope

# in your receiver
event = noclick.Webhook.verify(request_body, request_headers, secret)
print(event.type, event.data)

Every event is also kept for 30 days: noclick.Event.list(type="domain.*").

class Webhook

An endpoint on your server that receives events, signed with its secret. One created for a user (inside asUser, or with user) receives only that user's events; your account's own receive every event, your users' included.

Attributes
idstring
urlstring
eventsstring[]

The event types it receives; * matches the rest of a name.

enabledboolean
statusWebhookStatus

healthy, failing (its latest deliveries failed; retries continue) or disabled.

descriptionstring | null
successRate24hnumber | null

The share of attempts in the last 24 hours your server answered 2xx (null if none).

lastFailureJsonObject | null

The latest failed attempt: at, response_status and error (null if none).

userIdstring | null

The user whose events it receives (null: every event).

createdAtDate | null
secretstring | null

The signing secret (nk_whsec_...), on the object that created the webhook (create, or fromName when it created one), rotateSecret or revealSecret; any other read leaves it out.

createstatic

static create(url: string, options: WebhookCreateOptions): Promise<Webhook>

Start delivering events to url, an https address your server answers. secret is shown on the result this once.

Parameters
urlstringrequired
options.eventsreadonly string[]required

The event types it receives; operation.* or * for more at once.

options.descriptionstring
options.userUserOption

undefined = the ambient user (asUser), null = the account itself.

(defaults to the current user)
options.idempotencyKeystring

Makes a retried create safe: the same key returns the first result. Generated when omitted.

options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

Returns

The new webhook. Its secret is shown this once: store it where your receiver reads it.

fromNamestatic

static fromName(
  url: string,
  options?: CallOptions & {
    createIfMissing?: boolean;
    events?: readonly string[];
    description?: string;
  },
): Webhook

A lazy reference to the webhook delivering to url for the current (or given) user; url names it, since a scope has one webhook per address. With createIfMissing your code is the definition: it's created on first use with events (and description), and an existing one whose events or description differ is updated to match. secret holds the signing secret when this call created it; otherwise read it with revealSecret().

Parameters
urlstringrequired
options.createIfMissingboolean
options.eventsreadonly string[]
options.descriptionstring
options.userUserOption

undefined = the ambient user (asUser), null = the account itself.

(defaults to the current user)
options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

Returns

A webhook that loads (or is created, or brought in step with your definition) the first time it's used. When this call created it, secret holds its signing secret; otherwise read it with reveal_secret().

fromIdstatic

static fromId(webhookId: string, options?: CallOptions): Webhook

A lazy reference to a webhook by id.

Parameters
webhookIdstringrequired
options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

options.userUserOption

undefined = the ambient user (asUser), null = the account itself.

(defaults to the current user)
Returns

A webhook that loads the first time you read a field. Nothing is sent yet.

getstatic

static get(webhookId: string, options?: CallOptions): Promise<Webhook>

Load a webhook by id now; an unknown id raises noclick.NotFound.

Parameters
webhookIdstringrequired
options.userUserOption

undefined = the ambient user (asUser), null = the account itself.

(defaults to the current user)
options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

Returns

The webhook, loaded (without its secret).

liststatic

static list(options?: ReadOptions & { limit?: number }): Promise<Webhook[]>

Your account's webhooks (or the current user's, or those userFilter covers).

Parameters
options.limitnumber

Return at most this many.

options.userFilterUserFilter

Which users the read covers (user_id query), instead of acting as one (user).

options.userUserOption

undefined = the ambient user (asUser), null = the account itself.

(defaults to the current user)
options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

Returns

Every matching webhook, newest first, fetched page by page.

listPagestatic

static listPage(options?: ReadOptions & PageOptions): Promise<Page<Webhook>>

One page of webhooks; pass nextCursor back as cursor.

Parameters
options.cursorstring | null

The next_cursor of the page before; omit it for the first page.

options.limitnumber

Return at most this many.

options.userFilterUserFilter

Which users the read covers (user_id query), instead of acting as one (user).

options.userUserOption

undefined = the ambient user (asUser), null = the account itself.

(defaults to the current user)
options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

Returns

A page whose data holds the webhooks and whose next_cursor is None on the last page.

update

update(changes: WebhookChanges): Promise<this>

Change where it delivers, what it receives, or pause it (enabled: false).

Parameters
changesWebhookChangesrequired
Returns

This webhook, updated.

delete

delete(): Promise<void>

Delete the webhook; deliveries still retrying stop.

rotateSecret

rotateSecret(options?: { graceS?: number }): Promise<string>

Replace the signing secret. For graceS (default a day, at most 7 days) each delivery is signed with both, so deliveries keep verifying while you deploy the new one; graceS: 0 if the old one leaked. Resolves to the new secret, shown this once (also on hook.secret).

Parameters
options.graceSnumber
Returns

The new secret, shown this once (also on hook.secret).

revealSecret

revealSecret(): Promise<string>

Read the current signing secret, so a receiver that starts from code (or a webhook fromName found rather than created) can verify deliveries. Needs a key with the webhooks:reveal scope, which full access leaves out; every reveal is recorded with the key that made it.

Returns

The secret (nk_whsec_...), also on hook.secret.

test

test(eventType?): Promise<WebhookDelivery>

Send a sample event of eventType now, signed like a real one, and wait up to 10 seconds for your server's answer. It isn't retried and isn't recorded in Event.list.

Parameters
eventTypestringdefault "operation.succeeded"
Returns

The delivery: its status (succeeded or failed), response_status and response_body.

deliveries

deliveries(
  options?: {
    status?: WebhookDeliveryStatus | readonly WebhookDeliveryStatus[];
    event?: Event | string;
    limit?: number;
  },
): Promise<WebhookDelivery[]>

Deliveries newest first (the last 30 days), optionally only those with status or of one event.

Parameters
options.statusWebhookDeliveryStatus | readonly WebhookDeliveryStatus[]
options.eventEvent | string
options.limitnumber

Return at most this many.

Returns

Every matching delivery, fetched page by page (or the first limit).

redeliver

redeliver(delivery: WebhookDelivery | string): Promise<WebhookDelivery>

Send a delivery's event again now (the same event id), whatever its status.

Parameters
deliveryWebhookDelivery | stringrequired
Returns

The delivery, with this attempt counted in attempts and its outcome in status.

verifystatic

static verify(
  payload: string | Uint8Array | ArrayBuffer,
  headers: WebhookHeaders,
  secret: string | readonly string[],
  options?: { toleranceS?: number },
): Promise<Event>

Check a delivery in your receiver: payload is the raw body exactly as it arrived (not parsed and re-serialized), headers its headers and secret the webhook's, or several while you rotate. The NoClick-Signature header is t=<unix seconds>,v1=<hex>, where the hex is HMAC-SHA256 of "<t>.<body>" keyed with the secret; a timestamp more than toleranceS (300) from now is refused, so a captured delivery can't be replayed. Sends nothing. Throws WebhookSignatureError for a missing or malformed header, a stale timestamp, or no match.

Parameters
payloadstring | Uint8Array | ArrayBufferrequired
headersWebhookHeadersrequired
secretstring | readonly string[]required
options.toleranceSnumberdefault 300
Returns

The event the body carries, ready to act on.

WebhookDelivery

interface WebhookDelivery

One event sent (or on its way) to one webhook. status is pending (not tried yet), retrying (an attempt failed; nextRetryAt says when the next one is), succeeded (your server answered 2xx) or failed (three days of retries ran out). responseStatus and responseBody (its first 4 KB) are from the latest attempt; requestBody is the JSON sent.

Attributes
idstring
webhookIdstring | null
eventIdstring | null
eventTypestring | null
statusWebhookDeliveryStatus
attemptsnumber
responseStatusnumber | null
responseBodystring | null
requestBodystring | null
durationMsnumber | null
errorstring | null

Why the latest attempt failed (Answered 502, No answer within 10 seconds.); null once it succeeded.

nextRetryAtDate | null
createdAtDate | null
rawJsonObject

The response JSON exactly as the API sent it.

okboolean

Whether your server accepted it (a 2xx).

Event

class Event

Something that happened: an operation finished, a person answered, a domain went live. type names it (see Event.types()) and data is the object it's about, as its own read returns it (data.object names the kind, data.id its id). Delivered to your webhooks, and kept for 30 days.

Attributes
idstring
typestring

What happened, like operation.succeeded.

dataJsonObject

The object the event is about, as its own read returns it.

userIdstring | null

The user it happened for (null for your account's own).

createdAtDate | null
deliveriesWebhookDelivery[]

Where this event went: one delivery per webhook that subscribes to it. The full view carries them (Event.get(id), or await event.refresh() for one from a listing).

fromIdstatic

static fromId(eventId: string, options?: CallOptions): Event

A lazy reference to an event by id.

Parameters
eventIdstringrequired
options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

options.userUserOption

undefined = the ambient user (asUser), null = the account itself.

(defaults to the current user)
Returns

An event that loads the first time you read a field. Nothing is sent yet.

getstatic

static get(eventId: string, options?: CallOptions): Promise<Event>

Load an event by id now, with its deliveries; an unknown id (or one older than 30 days) throws NotFound.

Parameters
eventIdstringrequired
options.userUserOption

undefined = the ambient user (asUser), null = the account itself.

(defaults to the current user)
options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

Returns

The event, loaded.

liststatic

static list(options?: EventListOptions & { limit?: number }): Promise<Event[]>

Events newest first, from the last 30 days. Outside asUser you read your account's own events; userFilter reads your users'.

Parameters
options.typestring | readonly string[]

One type or several; * matches the rest of a name (domain.*).

options.sinceDate | string
options.untilDate | string
options.limitnumber

Return at most this many.

options.userFilterUserFilter

Which users the read covers (user_id query), instead of acting as one (user).

options.userUserOption

undefined = the ambient user (asUser), null = the account itself.

(defaults to the current user)
options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

Returns

Every matching event, fetched page by page (or the first limit).

listPagestatic

static listPage(options?: EventListOptions & PageOptions): Promise<Page<Event>>

One page of events; pass nextCursor back as cursor.

Parameters
options.cursorstring | null

The next_cursor of the page before; omit it for the first page.

options.limitnumber

Return at most this many.

options.typestring | readonly string[]

One type or several; * matches the rest of a name (domain.*).

options.sinceDate | string
options.untilDate | string
options.userFilterUserFilter

Which users the read covers (user_id query), instead of acting as one (user).

options.userUserOption

undefined = the ambient user (asUser), null = the account itself.

(defaults to the current user)
options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

Returns

A page whose data holds the events and whose next_cursor is None on the last page.

typesstatic

static types(options?: { client?: NoClick }): Promise<EventType[]>

The catalog of event types a webhook can subscribe to, in catalog order (grouped by area).

Parameters
options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

Returns

Every event type with what it means, in catalog order (grouped by area).

EventType

interface EventType

One kind of event: its name (operation.succeeded), what it means, and its area (the part before the first dot). emitted is whether NoClick sends it today: the rest are listed so your code can subscribe before the primitive behind them ships.

Attributes
namestring
descriptionstring
emittedboolean
rawJsonObject

The response JSON exactly as the API sent it.

areastring

What the event is about: operation, human, domain, ...