space.hooks.destinations
Create and change destinations, the URLs webhooks are sent to, and read their signing secrets.
A destination is a URL that webhooks are sent to, such as your order service. It says how to send them: the method, the authentication, how many a second, minute, or hour it can take, how many times to try each one, and how long to wait for an answer. Connections join sources to destinations.
Each webhook on its way to a destination is a delivery. It carries the body it was received with and the sender’s headers, then the destination’s authentication, then these headers:
| Header | Holds |
|---|---|
webhook-id | The event’s ID, the same on every attempt and every time it is sent again |
webhook-timestamp | When the attempt was sent, in seconds since 1 January 1970 |
webhook-signature | The Standard Webhooks signature, made with the destination’s whsec_ signing secret |
X-Hooks-Delivery-Id | The delivery’s ID |
X-Hooks-Attempt | Which attempt this is, from 1 |
Check the signature with space.hooks.verify().
Any 2xx answer delivers the webhook. Anything else, a time-out, or no connection is tried again after 30 seconds, then after twice as long each time, up to an hour between attempts. A Retry-After on a 429 or 503 answer is honoured when it asks for a longer wait, up to an hour. Redirects are not followed. After the last attempt the delivery is failed, and you can send it again.
A destination that fails 3 attempts in a row shows as failing until an attempt succeeds. When one of its deliveries fails for good, everyone who can manage the project’s webhooks gets an email, at most once a day for each destination.
A project holds at most 100 destinations. Every call below can also fail with the codes shared by all of space, such as missing_project, forbidden, timeout, or network_error. See SpaceError.
space.hooks.destinations.create()
Creates a destination and a signing secret for it.
space.hooks.destinations.create(
destination: DestinationInput,
options?: {
signal?: AbortSignal
timeoutMs?: number
}
): Promise<Destination>destination.namestring1 to 100 characters after trimming, without line breaks or other control characters. Unique in the project.
destination.urlstringAn https URL of at most 2,048 characters, without a
#part or a user name and password. Put credentials inauthinstead. A URL that leads to a private or reserved network address is refused when we send, and the attempt fails withblocked.destination.urlSecretbooleanHides the URL after saving, for a URL that holds a secret of its own. The destination’s
urlthen readsnull.falsewhen you leave it out.destination.methodstringPOST,PUT, orPATCH. Leave it out to send each webhook with the method it was received with.destination.statestringenabledorpaused.enabledwhen you leave it out. While a destination is paused, its deliveries wait and are sent once it is enabled again.destination.authDestinationAuthHow to authenticate to the URL. No authentication when you leave it out.
kindpicks one of these:{ kind: "none" }sends no authentication.{ kind: "bearer", token }sendsAuthorization: Bearer <token>.{ kind: "basic", username, password }sends HTTP basic authentication.usernameis 1 to 1,024 characters without colons.passwordis at most 1,024 characters.{ kind: "header", header, value }sends a header of your own, such asX-Api-Key.headermust be a valid header name and cannot be one we set: anyX-Hooks-header, thewebhook-signing headers,Content-Type,Content-Length,User-Agent,Host, or a header that manages the connection.
tokenandvalueare 1 to 4,096 characters. They may hold tabs, but not line breaks or other control characters. None of these can be read back: the destination shows onlykind, andheaderfor theheaderkind.destination.rateLimit{ count, period } | nullAt most
countsends eachperiod.countis 1 to 100,000 andperiodissecond,minute, orhour. Sends over the limit wait their turn; none are dropped. No limit when you leave it out or passnull.destination.maxAttemptsintegerHow many times to try each delivery, 1 to 20. 6 when you leave it out.
destination.timeoutSecondsintegerHow long each attempt waits for an answer, 1 to 60 seconds. 30 when you leave it out.
options.signalAbortSignalCancels the call when it aborts. The promise then rejects with
aborted.options.timeoutMsnumberThe longest each request may take, in milliseconds. The client’s
timeoutMswhen you leave it out, 30,000 by default.
Returns a promise of the new destination. Read its signing secret with signingSecret(). create() is not sent again after a network failure or a timeout, because the destination may already exist.
| Code | When |
|---|---|
validation_failed | A field breaks a rule above, or is not listed. issues names each field, such as auth.header |
hooks_name_taken | Another destination in the project has that name |
hooks_limit_reached | The project already holds 100 destinations |
import { space } from "digitospace"
const destination = await space.hooks.destinations.create({
name: "Orders, Dar es Salaam",
url: "https://orders.example.co.tz/webhooks/payments",
method: "POST",
auth: { kind: "bearer", token: process.env.ORDERS_TOKEN ?? "" },
rateLimit: { count: 10, period: "second" },
maxAttempts: 10,
timeoutSeconds: 15,
})
console.log(destination.id, destination.auth.kind)space.hooks.destinations.get()
Reads one destination.
space.hooks.destinations.get(
destination: string,
options?: {
signal?: AbortSignal
timeoutMs?: number
}
): Promise<Destination>destinationstringThe destination’s ID, 26 lowercase letters and digits.
options.signalAbortSignalCancels the call when it aborts.
options.timeoutMsnumberThe longest each request may take, in milliseconds.
Returns a promise of the destination.
| Code | When |
|---|---|
validation_failed | The ID is not 26 lowercase letters and digits. Nothing is sent |
not_found | The project has no destination with that ID |
import { space } from "digitospace"
const destination = await space.hooks.destinations.get("01m3m5gpbttxs7xnhk0jgyt4wa")
if (destination.failing) {
console.log(`Failing since ${destination.failingSince}`)
}space.hooks.destinations.list()
Lists the project’s destinations, newest first.
space.hooks.destinations.list(options?: {
signal?: AbortSignal
timeoutMs?: number
}): Paged<Destination>options.signalAbortSignalCancels the call when it aborts.
options.timeoutMsnumberThe longest each request may take, in milliseconds.
Returns a Paged<Destination> at once, without a request. It reads the destinations 200 at a time as you go: loop over it with for await, collect every destination with .all(), or read one page with .page(cursor), which resolves to { items, nextCursor }.
import { space } from "digitospace"
const destinations = await space.hooks.destinations.list().all()
for (const destination of destinations.filter((found) => found.failing)) {
console.log(destination.name, destination.failuresInRow)
}space.hooks.destinations.update()
Changes some of a destination’s fields and keeps the rest. It reads the destination first, then saves it whole with your changes, so two updates at the same time can undo each other.
space.hooks.destinations.update(
destination: string,
change: Partial<DestinationInput>,
options?: {
signal?: AbortSignal
timeoutMs?: number
}
): Promise<Destination>destinationstringThe destination’s ID.
changePartial<DestinationInput>The fields to change, with the same rules as
create(). A field you leave out keeps its value:- A hidden URL stays hidden and keeps working unless you pass a new
urlorurlSecret: false. To show a hidden URL again, passurlSecret: falsewith theurl. - The saved authentication is kept unless you pass
auth. Pass{ kind: "none" }to remove it. rateLimit: nullremoves the rate limit.
- A hidden URL stays hidden and keeps working unless you pass a new
options.signalAbortSignalCancels the call when it aborts.
options.timeoutMsnumberThe longest each request may take, in milliseconds.
Returns a promise of the changed destination.
| Code | When |
|---|---|
validation_failed | A field breaks a rule. issues names each field |
not_found | The project has no destination with that ID |
hooks_name_taken | Another destination in the project has that name |
import { space } from "digitospace"
await space.hooks.destinations.update("01m3m5gpbttxs7xnhk0jgyt4wa", {
timeoutSeconds: 30,
rateLimit: { count: 300, period: "minute" },
})space.hooks.destinations.pause()
Pauses a destination. New deliveries are still made for it, and every delivery waits until you resume it. Events are kept for 30 days, so a delivery still waiting after that is deleted with its event.
space.hooks.destinations.pause(
destination: string,
options?: {
signal?: AbortSignal
timeoutMs?: number
}
): Promise<Destination>destinationstringThe destination’s ID.
options.signalAbortSignalCancels the call when it aborts.
options.timeoutMsnumberThe longest each request may take, in milliseconds.
The same as update(destination, { state: "paused" }), with the same errors. Returns a promise of the paused destination.
import { space } from "digitospace"
const destination = await space.hooks.destinations.pause("01m3m5gpbttxs7xnhk0jgyt4wa")
console.log(destination.state)space.hooks.destinations.resume()
Enables a paused destination. Its waiting deliveries are sent, within its rate limit.
space.hooks.destinations.resume(
destination: string,
options?: {
signal?: AbortSignal
timeoutMs?: number
}
): Promise<Destination>destinationstringThe destination’s ID.
options.signalAbortSignalCancels the call when it aborts.
options.timeoutMsnumberThe longest each request may take, in milliseconds.
The same as update(destination, { state: "enabled" }), with the same errors. Returns a promise of the enabled destination.
import { space } from "digitospace"
await space.hooks.destinations.resume("01m3m5gpbttxs7xnhk0jgyt4wa")space.hooks.destinations.signingSecret()
Reads the secret the destination’s deliveries are signed with. It starts with whsec_. Keep it on the server that receives the deliveries, and pass it to space.hooks.verify().
space.hooks.destinations.signingSecret(
destination: string,
options?: {
signal?: AbortSignal
timeoutMs?: number
}
): Promise<string>destinationstringThe destination’s ID.
options.signalAbortSignalCancels the call when it aborts.
options.timeoutMsnumberThe longest each request may take, in milliseconds.
Returns a promise of the secret.
| Code | When |
|---|---|
validation_failed | The ID is not 26 lowercase letters and digits. Nothing is sent |
not_found | The project has no destination with that ID |
forbidden | The API key may not change the project’s webhooks, which reading a secret needs |
import { space } from "digitospace"
const secret = await space.hooks.destinations.signingSecret("01m3m5gpbttxs7xnhk0jgyt4wa")
console.log(`Set WEBHOOK_SECRET to ${secret} on the orders server`)space.hooks.destinations.resetSigningSecret()
Makes a new signing secret and returns it. Deliveries are signed with the new secret from the next attempt on, and the old one stops matching at once, so update the receiving server straight away.
space.hooks.destinations.resetSigningSecret(
destination: string,
options?: {
signal?: AbortSignal
timeoutMs?: number
}
): Promise<string>destinationstringThe destination’s ID.
options.signalAbortSignalCancels the call when it aborts.
options.timeoutMsnumberThe longest each request may take, in milliseconds.
Returns a promise of the new secret. It is not sent again after a network failure or a timeout, because the secret may already have changed. Read it with signingSecret() if the call fails that way.
| Code | When |
|---|---|
validation_failed | The ID is not 26 lowercase letters and digits. Nothing is sent |
not_found | The project has no destination with that ID |
import { space } from "digitospace"
const secret = await space.hooks.destinations.resetSigningSecret(
"01m3m5gpbttxs7xnhk0jgyt4wa"
)
console.log(secret.startsWith("whsec_"))space.hooks.destinations.delete()
Deletes a destination, with its connections and every delivery to it. Deliveries still waiting are not sent. This cannot be undone.
space.hooks.destinations.delete(
destination: string,
options?: {
signal?: AbortSignal
timeoutMs?: number
}
): Promise<void>destinationstringThe destination’s ID.
options.signalAbortSignalCancels the call when it aborts.
options.timeoutMsnumberThe longest each request may take, in milliseconds.
Returns a promise that resolves when the destination is gone.
| Code | When |
|---|---|
validation_failed | The ID is not 26 lowercase letters and digits. Nothing is sent |
not_found | The project has no destination with that ID |
import { space } from "digitospace"
await space.hooks.destinations.delete("01m3m5gpbttxs7xnhk0jgyt4wa")Last updated