Allomancy

Managing webhooks with the API

Create, update, enable, disable, test, and delete webhook subscriptions through the API, and read their delivery failures.

This page is the API counterpart of the Webhooks tab of the Integrations page, where the store owner manages webhooks in Allomancy Stores. Both manage the same subscriptions.

A webhook subscription tells Allomancy where to send your store's events and which events to send. These endpoints manage subscriptions and read their delivery failures. Reading needs the webhooks:read scope and every change needs webhooks:write; a key without the required scope gets 403. For what arrives at your URL and how to verify it, see Webhooks. Field shapes are on the Object reference page.

Subscription rules

  • At most 3 active subscriptions per store. Inactive subscriptions do not count toward the limit.
  • Names are unique among active subscriptions, and 3 to 128 characters long.
  • At least one event type. Choose from sale.completed, payment.received, marketplace.sale.completed, redelivery.completed, and marketplace.redelivery.completed; duplicates are collapsed. test.ping cannot be selected. If an event type stops being selectable, a subscription that already has it keeps it on update, but it cannot be newly added.
  • The URL must meet the destination URL requirements. A subscription with an http URL has insecureTransport set to true.
  • The signing secret must be a whsec_ secret as described in Signing secrets and rotation. It is never returned.

A subscription's status says whether it receives events, and why not:

StatusMeaning
activeThe subscription receives events. isActive is true.
disabledByMerchantThe subscription was disabled on purpose, with Disable a subscription or in Allomancy Stores.
disabledByDeliveryFailuresThe subscription was disabled automatically after 10 consecutive failed events (see Automatic disabling).

List subscriptions

GET /v1/webhooks

Returns a cursor-paginated list of the store's subscriptions. Scope: webhooks:read. Query parameters limit and cursor are optional; see Pagination and errors.

An optional active (boolean) query parameter narrows the list to active (true) or inactive (false) subscriptions.

curl --fail-with-body "https://integrations.allomancy.net/v1/webhooks?active=true" \
  -H "X-API-Key: allo_live_8Kd2...zQ.Hk9...4w"
import requests

response = requests.get(
    "https://integrations.allomancy.net/v1/webhooks",
    headers={"X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w"},
    params={"active": "true"},
)
print(response.json())
const response = await fetch(
  "https://integrations.allomancy.net/v1/webhooks?active=true",
  {
    headers: { "X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w" },
  },
);
console.log(await response.json());
<?php
$ch = curl_init("https://integrations.allomancy.net/v1/webhooks?active=true");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: allo_live_8Kd2...zQ.Hk9...4w"]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
{
  "data": [
    {
      "id": "0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
      "name": "Order sync",
      "url": "https://hooks.example.com/allomancy",
      "eventTypes": ["sale.completed", "payment.received"],
      "isActive": true,
      "consecutiveFailures": 0,
      "disabledAt": null,
      "status": "active",
      "insecureTransport": false,
      "created": "2026-10-21T14:00:00.127834Z",
      "updated": "2026-10-21T14:00:00.127834Z"
    }
  ],
  "pagination": { "nextCursor": null, "previousCursor": null, "limit": 100 }
}

Each row is a WebhookSubscriptionListDto. Status: 200, or 400 for a bad pagination parameter.

Get a subscription

GET /v1/webhooks/{id}

Returns one subscription by its UUID. Scope: webhooks:read.

curl --fail-with-body https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b \
  -H "X-API-Key: allo_live_8Kd2...zQ.Hk9...4w"
import requests

response = requests.get(
    "https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    headers={"X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w"},
)
print(response.json())
const response = await fetch(
  "https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
  {
    headers: { "X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w" },
  },
);
console.log(await response.json());
<?php
$ch = curl_init("https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: allo_live_8Kd2...zQ.Hk9...4w"]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;

The response is a WebhookSubscriptionListDto. Status: 200, or 404 if no subscription with that id belongs to the store.

Create a subscription

POST /v1/webhooks

Creates an active subscription. Scope: webhooks:write. The body is a WebhookSubscriptionRequest. The URL is checked against the destination URL requirements when you save, including the DNS lookup.

curl --fail-with-body https://integrations.allomancy.net/v1/webhooks \
  -H "X-API-Key: allo_live_8Kd2...zQ.Hk9...4w" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Order sync", "url": "https://hooks.example.com/allomancy", "signingSecret": "whsec_8f2Kx...Qm4=", "eventTypes": ["sale.completed", "payment.received"] }'
import requests

response = requests.post(
    "https://integrations.allomancy.net/v1/webhooks",
    headers={"X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w"},
    json={
        "name": "Order sync",
        "url": "https://hooks.example.com/allomancy",
        "signingSecret": "whsec_8f2Kx...Qm4=",
        "eventTypes": ["sale.completed", "payment.received"],
    },
)
print(response.json())
const response = await fetch("https://integrations.allomancy.net/v1/webhooks", {
  method: "POST",
  headers: {
    "X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Order sync",
    url: "https://hooks.example.com/allomancy",
    signingSecret: "whsec_8f2Kx...Qm4=",
    eventTypes: ["sale.completed", "payment.received"],
  }),
});
console.log(await response.json());
<?php
$ch = curl_init("https://integrations.allomancy.net/v1/webhooks");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "X-API-Key: allo_live_8Kd2...zQ.Hk9...4w",
    "Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    "name" => "Order sync",
    "url" => "https://hooks.example.com/allomancy",
    "signingSecret" => "whsec_8f2Kx...Qm4=",
    "eventTypes" => ["sale.completed", "payment.received"],
]));
$response = curl_exec($ch);
curl_close($ch);
echo $response;

On success the response is 201 Created with a Location header pointing at the new subscription, and the body is the created WebhookSubscriptionListDto. A new subscription receives only events that occur from the moment it is created. Status: 201, or 400 when the body fails validation, the store already has 3 active subscriptions, an active subscription already uses the name, the URL is refused, or its host name cannot be found or checked.

Update a subscription

PUT /v1/webhooks/{id}

Changes a subscription's name, URL, event types, and optionally its signing secret. Scope: webhooks:write. The body is a WebhookSubscriptionRequest. Leave out signingSecret, or send null, to keep the current secret; send a new one to rotate it. An update never changes whether the subscription is active, its consecutiveFailures, or its disabledAt.

A new URL applies to every later attempt, including deliveries already waiting to be retried.

curl --fail-with-body https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b \
  -X PUT \
  -H "X-API-Key: allo_live_8Kd2...zQ.Hk9...4w" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Order sync", "url": "https://hooks.example.com/allomancy/v2", "signingSecret": "whsec_Zt7mR...1kA=", "eventTypes": ["sale.completed"] }'
import requests

response = requests.put(
    "https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    headers={"X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w"},
    json={
        "name": "Order sync",
        "url": "https://hooks.example.com/allomancy/v2",
        "signingSecret": "whsec_Zt7mR...1kA=",
        "eventTypes": ["sale.completed"],
    },
)
print(response.json())
const response = await fetch(
  "https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
  {
    method: "PUT",
    headers: {
      "X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "Order sync",
      url: "https://hooks.example.com/allomancy/v2",
      signingSecret: "whsec_Zt7mR...1kA=",
      eventTypes: ["sale.completed"],
    }),
  },
);
console.log(await response.json());
<?php
$ch = curl_init("https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "X-API-Key: allo_live_8Kd2...zQ.Hk9...4w",
    "Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    "name" => "Order sync",
    "url" => "https://hooks.example.com/allomancy/v2",
    "signingSecret" => "whsec_Zt7mR...1kA=",
    "eventTypes" => ["sale.completed"],
]));
$response = curl_exec($ch);
curl_close($ch);
echo $response;

The response is the updated WebhookSubscriptionListDto. Status: 200, 400, or 404.

Delete a subscription

DELETE /v1/webhooks/{id}

Permanently deletes a subscription together with its failure history. Deliveries still waiting to be retried are dropped. Scope: webhooks:write. To stop deliveries but keep the subscription and its history, disable it instead.

curl --fail-with-body https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b \
  -X DELETE \
  -H "X-API-Key: allo_live_8Kd2...zQ.Hk9...4w"
import requests

response = requests.delete(
    "https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    headers={"X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w"},
)
print(response.status_code)
const response = await fetch(
  "https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
  {
    method: "DELETE",
    headers: { "X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w" },
  },
);
console.log(response.status);
<?php
$ch = curl_init("https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: allo_live_8Kd2...zQ.Hk9...4w"]);
curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

On success the response is 204 No Content. Status: 204 or 404.

Enable a subscription

POST /v1/webhooks/{id}/actions/enable

Makes a subscription active again. Scope: webhooks:write. The request takes no body. Enabling resets consecutiveFailures to 0 and clears disabledAt; the failure history is kept. The subscription receives events that occur from then on. Events that occurred while it was inactive are normally not sent; one still being processed when you enable the subscription can arrive, and so can a delivery whose retry comes due after you enable it.

Enabling a subscription that is already active returns it unchanged, without resetting consecutiveFailures. Enabling fails with 400 when the store already has 3 active subscriptions, or when an active subscription already uses the same name.

curl --fail-with-body https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/actions/enable \
  -X POST \
  -H "X-API-Key: allo_live_8Kd2...zQ.Hk9...4w"
import requests

response = requests.post(
    "https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/actions/enable",
    headers={"X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w"},
)
print(response.json())
const response = await fetch(
  "https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/actions/enable",
  {
    method: "POST",
    headers: { "X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w" },
  },
);
console.log(await response.json());
<?php
$ch = curl_init("https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/actions/enable");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: allo_live_8Kd2...zQ.Hk9...4w"]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;

The response is the WebhookSubscriptionListDto. Status: 200, 400, or 404.

Disable a subscription

POST /v1/webhooks/{id}/actions/disable

Stops deliveries to a subscription without deleting it. Scope: webhooks:write. The request takes no body. The subscription's status becomes disabledByMerchant and disabledAt records when; consecutiveFailures and the failure history are kept. A delivery waiting to be retried is dropped if the subscription is still disabled when its retry comes due.

Disabling a subscription that is already inactive returns it unchanged, keeping its current status.

curl --fail-with-body https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/actions/disable \
  -X POST \
  -H "X-API-Key: allo_live_8Kd2...zQ.Hk9...4w"
import requests

response = requests.post(
    "https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/actions/disable",
    headers={"X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w"},
)
print(response.json())
const response = await fetch(
  "https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/actions/disable",
  {
    method: "POST",
    headers: { "X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w" },
  },
);
console.log(await response.json());
<?php
$ch = curl_init("https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/actions/disable");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: allo_live_8Kd2...zQ.Hk9...4w"]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;

The response is the WebhookSubscriptionListDto. Status: 200 or 404.

Send a test event

POST /v1/webhooks/{id}/actions/test

Sends one signed test.ping event to the subscription's stored URL, so you can check your receiver end to end. Scope: webhooks:write. The request takes no body. You can test a subscription in any state, including a disabled one. A test is never counted toward consecutiveFailures and is never recorded as a delivery failure.

When you send the test, the stored URL is checked against the static destination URL requirements; a URL that fails them returns 400 with the message The URL is not allowed. The host name lookup and the public-address check run when the ping is delivered, so a test accepted with 202 can still fail at delivery. A failed ping is never recorded as a delivery failure.

You can send one test per subscription per minute. A second test within the minute returns 429 with a problem+json body carrying the message You can send a test once per minute. and a Retry-After header giving the number of whole seconds to wait.

curl --fail-with-body https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/actions/test \
  -X POST \
  -H "X-API-Key: allo_live_8Kd2...zQ.Hk9...4w"
import requests

response = requests.post(
    "https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/actions/test",
    headers={"X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w"},
)
print(response.json())
const response = await fetch(
  "https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/actions/test",
  {
    method: "POST",
    headers: { "X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w" },
  },
);
console.log(await response.json());
<?php
$ch = curl_init("https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/actions/test");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: allo_live_8Kd2...zQ.Hk9...4w"]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
{
  "eventId": "0192a700-1f2e-7d3c-8b4a-5968778695a4",
  "queuedAt": "2026-10-21T15:30:00.482913Z"
}

On success the response is 202 Accepted with a WebhookTestQueuedDto: the test event's id, which arrives as its webhook-id, and when it was queued. Status: 202, 400, 404, or 429.

List delivery failures

GET /v1/webhooks/{id}/failures

Returns a cursor-paginated list of the subscription's delivery failures, newest first. Scope: webhooks:read. Query parameters limit (default 100, at most 1000) and cursor are optional; see Pagination and errors.

Failed events are recorded here (see Responding and retries). Records are kept for 30 days, and the response's retentionDays field states that period. Test events and problems on Allomancy's side never appear here.

curl --fail-with-body "https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/failures?limit=50" \
  -H "X-API-Key: allo_live_8Kd2...zQ.Hk9...4w"
import requests

response = requests.get(
    "https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/failures",
    headers={"X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w"},
    params={"limit": 50},
)
print(response.json())
const response = await fetch(
  "https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/failures?limit=50",
  {
    headers: { "X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w" },
  },
);
console.log(await response.json());
<?php
$ch = curl_init("https://integrations.allomancy.net/v1/webhooks/0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/failures?limit=50");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: allo_live_8Kd2...zQ.Hk9...4w"]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
{
  "data": [
    {
      "id": "0192a9e1-7c4b-7d20-9e31-a4b5c6d7e8f9",
      "subscriptionId": "0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
      "subscriptionName": "Order sync",
      "eventId": "0192a6f4-3b1c-7d2e-8f40-5a6b7c8d9e0f",
      "eventType": "sale.completed",
      "failureKind": "http",
      "responseStatus": 500,
      "attemptCount": 10,
      "diagnosticCode": "http_status",
      "error": "The endpoint answered with HTTP 500.",
      "created": "2026-10-22T05:46:12.553201Z"
    }
  ],
  "pagination": { "nextCursor": null, "previousCursor": null, "limit": 50 },
  "retentionDays": 30
}

Each row is a WebhookFailureDto. Status: 200, 400 for a bad pagination parameter, or 404.

The failureKind groups failures broadly, and the diagnosticCode names the cause. The error field always carries the fixed explanation for its code:

diagnosticCodefailureKinderror
http_statushttpThe endpoint answered with HTTP 500., with your endpoint's status in place of 500
redirect_refusedhttpThe endpoint answered with a redirect. Redirects are not followed.
timeouttimeoutThe endpoint did not answer in time.
connection_failednetworkA connection to the endpoint could not be completed.
dns_failurenetworkThe host name could not be found.
invalid_urlvalidationThe URL is not allowed.
invalid_secretvalidationThe signing secret is not valid.
destination_refusedvalidationThe destination is not allowed.

A failure recorded with the code unclassified carries the text Delivery failed.

responseStatus is the HTTP status your endpoint answered with, or null when no answer was received. attemptCount is the number of attempts that reached your endpoint or tried to; it is 0 when the event failed a check before any attempt.

List event types

GET /v1/webhooks/event-types

Returns every event type, including whether a subscription can select it. Scope: webhooks:read. The response is a JSON array, not the paginated envelope.

curl --fail-with-body https://integrations.allomancy.net/v1/webhooks/event-types \
  -H "X-API-Key: allo_live_8Kd2...zQ.Hk9...4w"
import requests

response = requests.get(
    "https://integrations.allomancy.net/v1/webhooks/event-types",
    headers={"X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w"},
)
print(response.json())
const response = await fetch("https://integrations.allomancy.net/v1/webhooks/event-types", {
  headers: { "X-API-Key": "allo_live_8Kd2...zQ.Hk9...4w" },
});
console.log(await response.json());
<?php
$ch = curl_init("https://integrations.allomancy.net/v1/webhooks/event-types");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: allo_live_8Kd2...zQ.Hk9...4w"]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
[
  {
    "name": "sale.completed",
    "description": "A sale completed at one of your vendors.",
    "isSelectable": true
  },
  {
    "name": "payment.received",
    "description": "A payment terminal received L$.",
    "isSelectable": true
  },
  {
    "name": "marketplace.sale.completed",
    "description": "A sale completed on the Second Life Marketplace.",
    "isSelectable": true
  },
  {
    "name": "redelivery.completed",
    "description": "A product was redelivered to a customer.",
    "isSelectable": true
  },
  {
    "name": "marketplace.redelivery.completed",
    "description": "A Second Life Marketplace redelivery was recorded.",
    "isSelectable": true
  },
  {
    "name": "test.ping",
    "description": "A test sent from the dashboard or the API.",
    "isSelectable": false
  }
]

Each entry is a WebhookEventTypeDto. Status: 200.

On this page