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, andmarketplace.redelivery.completed; duplicates are collapsed.test.pingcannot 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
httpURL hasinsecureTransportset totrue. - 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:
| Status | Meaning |
|---|---|
active | The subscription receives events. isActive is true. |
disabledByMerchant | The subscription was disabled on purpose, with Disable a subscription or in Allomancy Stores. |
disabledByDeliveryFailures | The subscription was disabled automatically after 10 consecutive failed events (see Automatic disabling). |
List subscriptions
GET /v1/webhooksReturns 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/webhooksCreates 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/enableMakes 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/disableStops 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/testSends 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}/failuresReturns 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:
diagnosticCode | failureKind | error |
|---|---|---|
http_status | http | The endpoint answered with HTTP 500., with your endpoint's status in place of 500 |
redirect_refused | http | The endpoint answered with a redirect. Redirects are not followed. |
timeout | timeout | The endpoint did not answer in time. |
connection_failed | network | A connection to the endpoint could not be completed. |
dns_failure | network | The host name could not be found. |
invalid_url | validation | The URL is not allowed. |
invalid_secret | validation | The signing secret is not valid. |
destination_refused | validation | The 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-typesReturns 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.