Appearance
Webhooks endpoints
These REST routes manage the endpoints that receive Link Manager Pro events. You can create and edit a webhook, send a test, read its recent deliveries and reveal or regenerate its signing secret.
A webhook is an HTTPS address that receives an HTTP request when a Link Manager Pro event happens. Sign-in and the response envelope are in the REST API overview.
All paths are relative to /wp-json/link-manager-pro/v1. Examples use https://example.com and USER:APPLICATION_PASSWORD. Inside WordPress, send the X-WP-Nonce header instead.
Every route here needs an administrator (manage_options). The request your receiver gets, its headers and signature are described in Events, webhooks and AutomatorWP. The screen is explained in Webhooks and AutomatorWP.
A validation failure is a 422 with message and an errors object that lists messages per field. A missing webhook is a 404 with code, message, data.status and status.
json
{
"code": "not_found",
"message": "Webhook not found.",
"data": { "status": 404 },
"status": 404
}The webhook object
Every route that returns a webhook uses these fields. The signing secret is never in the list. The list and PUT return only a masked form of it.
| Field | Type | What it holds |
|---|---|---|
id | integer | The webhook ID. |
name | string | A label for you. |
target_url | string | The address that receives the request. |
events | array of strings | The events it receives: link_clicked, broken_link_found, link_created, broken_link_fixed. |
status | string | active or paused. A paused webhook receives nothing. |
method | string | The HTTP method: GET, POST, PUT, PATCH or DELETE. |
ignore_signing | string | yes sends requests without a signature. no signs them. |
headers | object | Custom headers added to each request, as name and value strings. Empty as [] when there are none. |
has_secret | string | yes or no. A secret is always made on create, so this is yes unless the stored secret cannot be read. |
secret_unreadable | string | yes when a secret is stored but can't be decrypted, for example after the site's AUTH_KEY changed. Then has_secret is no and secret_mask is empty. Otherwise no. |
secret_mask | string | The secret with every character but the last 4 replaced by •. Empty when there is no secret. |
Signing. With ignore_signing set to no and a secret stored, each request carries an X-LMP-Signature header. Its value is sha256= and the hex HMAC-SHA256 of t=<timestamp>.<body>, made with the raw secret. Verify the signature has receiver code. Setting ignore_signing to yes keeps the secret but stops signing. With ignore_signing set to no and an unreadable secret, the delivery fails with secret_unreadable and is never sent unsigned. Regenerate the secret to fix it.
The secret. The plugin stores it encrypted. Create and regenerate return the raw value once in a secret field. GET /webhooks/{id}/secret reveals it at any time.
Failures. The plugin counts failed deliveries in a row for each webhook. After 15 in a row it sets status to paused. Failed deliveries with secret_unreadable count too. The count is not part of the object. Saving a new secret resets it to 0. To resume a paused webhook, send PUT /webhooks/{id} with status set to active. Resuming does not reset the count. Only a delivered request or a new secret does.
The delivery object
A delivery is one queued event for one webhook. The plugin sends it in the background, up to 5 tries per delivery.
| Field | Type | What it holds |
|---|---|---|
id | integer | The delivery ID. |
webhook_id | integer | The webhook it belongs to. |
event | string | The event name, for example link_clicked. |
status | string | pending, sending, success or failed. |
attempts | integer | Tries made so far. |
response_code | integer or null | The HTTP status of the last try. 0 when the receiver gave no answer. null before the first try. |
last_error | string or null | Why the last try failed, for example http_500 or secret_unreadable. null when there is no error. |
created_at | string or null | When the event was queued, UTC Y-m-d H:i:s. |
delivered_at | string or null | When the receiver accepted it, UTC Y-m-d H:i:s. null until then. |
Status values.
| Status | Meaning |
|---|---|
pending | Queued, or waiting for the next try after a failed one. |
sending | A background run has taken it and is sending it now. |
success | The receiver answered with a status from 200 to 299. |
failed | The fifth try failed, or the webhook was deleted. |
A try that gets any other status, a timeout, or a blocked address fails. A failed try that is not the fifth puts the delivery back to pending with a wait of about 1 minute, then 5 minutes, then 30 minutes, then 2 hours. Details are in Events, webhooks and AutomatorWP.
Webhooks
List webhooks
GET /webhooks
Returns every webhook, oldest first. There is no paging.
Permission: manage_options (Permissions)
Query parameters: None.
Response: 200. data.data is an array of webhook objects.
bash
curl -u USER:APPLICATION_PASSWORD \
https://example.com/wp-json/link-manager-pro/v1/webhooksjson
{
"success": true,
"data": {
"data": [
{
"id": 31,
"name": "Zapier clicks",
"target_url": "https://hooks.example.com/catch/123",
"events": ["link_clicked", "link_created"],
"status": "active",
"method": "POST",
"ignore_signing": "no",
"headers": { "X-Source": "lmp" },
"has_secret": "yes",
"secret_mask": "••••••••••••••••••••••••••••••••••••a1b2"
}
]
}
}Create a webhook
POST /webhooks
Creates a webhook and returns it with its signing secret. A secret is generated when you send none.
Permission: manage_options
Body parameters:
| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
name | string | Yes | none | A label. 191 bytes or fewer. |
target_url | string | Yes | none | Where requests go. A valid URL of 2048 bytes or fewer. It must use https and its host must resolve only to public addresses. Any port is allowed. |
events | array of strings | Yes | none | At least one of link_clicked, broken_link_found, link_created, broken_link_fixed. |
status | string | No | active | active or paused. |
method | string | No | POST | GET, POST, PUT, PATCH or DELETE. A GET request has no body. |
ignore_signing | boolean or string | No | false | true, 1, yes or on turns signing off. Anything else leaves it on. |
secret | string | No | generated | The signing secret, 255 bytes or fewer. Empty or missing generates a random 40 character one. |
headers | object | No | none | Custom headers as name and value pairs. |
Headers. Names and values are cleaned as plain text. An entry with a non-string name or a value that is not a string, number or boolean is dropped. The code sets no limit on the number of headers. A header named Content-Type, X-LMP-Event, X-LMP-Delivery, X-LMP-Timestamp or X-LMP-Signature is stored but never sent, whatever its case.
Response: 200, with the message "Webhook created." data is the webhook object plus one field:
| Field | Type | What it holds |
|---|---|---|
secret | string | The raw signing secret. |
bash
curl -X POST -u USER:APPLICATION_PASSWORD \
-H "Content-Type: application/json" \
-d '{
"name": "Zapier clicks",
"target_url": "https://hooks.example.com/catch/123",
"events": ["link_clicked", "link_created"],
"headers": { "X-Source": "lmp" }
}' \
https://example.com/wp-json/link-manager-pro/v1/webhooksjson
{
"success": true,
"data": {
"id": 31,
"name": "Zapier clicks",
"target_url": "https://hooks.example.com/catch/123",
"events": ["link_clicked", "link_created"],
"status": "active",
"method": "POST",
"ignore_signing": "no",
"headers": { "X-Source": "lmp" },
"has_secret": "yes",
"secret_mask": "••••••••••••••••••••••••••••••••••••a1b2",
"secret": "Qz7hT0w3Lk9mN2pR5vX8cB1dF4gJ6sUyaa1b2"
},
"message": "Webhook created."
}Errors:
| Status | When |
|---|---|
422 | name, target_url or events is missing or empty. |
422 | name is over 191 bytes, target_url is not a URL or is over 2048 bytes, or secret is over 255 bytes. |
422 | An event is not one of the four. The error is keyed events.0, events.1 and so on. |
422 | status or method is not an allowed value. |
422 | target_url does not use https, does not resolve, or resolves to a private or reserved address. errors.target_url says "The webhook URL must use https and point to a public host." |
Update a webhook
PUT /webhooks/{id}
Changes the fields you send and leaves the others as they are. It returns the updated webhook without the secret.
Permission: manage_options
Path parameters:
| Name | Type | What it is |
|---|---|---|
id | integer | The webhook ID. |
Body parameters: the same fields as Create a webhook, all optional, with the same limits and allowed values.
| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
name | string | No | unchanged | A new label. |
target_url | string | No | unchanged | A new address. It must pass the same https and public host check. |
events | array of strings | No | unchanged | Replaces the list. A name that is not allowed is a 422, as on create. |
status | string | No | unchanged | active or paused. |
method | string | No | unchanged | The new HTTP method. |
ignore_signing | boolean or string | No | unchanged | Same values as on create. When the key is present, the value is applied. |
secret | string | No | unchanged | A new secret. An empty value is ignored, and so is a value equal to the stored secret. A new secret resets the failure count. Setting status to active on a paused webhook resets it too. |
headers | object | No | unchanged | Replaces all custom headers. Send {} or null to remove them. |
Response: 200, with the message "Webhook updated." data is the webhook object, without secret.
bash
curl -X PUT -u USER:APPLICATION_PASSWORD \
-H "Content-Type: application/json" \
-d '{ "status": "paused" }' \
https://example.com/wp-json/link-manager-pro/v1/webhooks/31json
{
"success": true,
"data": {
"id": 31,
"name": "Zapier clicks",
"target_url": "https://hooks.example.com/catch/123",
"events": ["link_clicked", "link_created"],
"status": "paused",
"method": "POST",
"ignore_signing": "no",
"headers": { "X-Source": "lmp" },
"has_secret": "yes",
"secret_mask": "••••••••••••••••••••••••••••••••••••a1b2"
},
"message": "Webhook updated."
}Errors:
| Status | When |
|---|---|
404 | No webhook has that ID. |
422 | A sent field breaks a limit or allowed value listed under Create a webhook. |
422 | target_url fails the https and public host check. errors.target_url holds the message. |
Delete a webhook
DELETE /webhooks/{id}
Removes a webhook and all of its stored deliveries.
Permission: manage_options
Path parameters:
| Name | Type | What it is |
|---|---|---|
id | integer | The webhook ID. |
Response: 200, with the message "Webhook deleted." data is null.
json
{
"success": true,
"data": null,
"message": "Webhook deleted."
}Errors:
| Status | When |
|---|---|
404 | No webhook has that ID. |
List a webhook's deliveries
GET /webhooks/{id}/deliveries
Returns the 50 most recent deliveries of one webhook, newest first.
Permission: manage_options
Path parameters:
| Name | Type | What it is |
|---|---|---|
id | integer | The webhook ID. |
Query parameters: None. There is no paging, and no way to ask for more than 50.
Response: 200. data.data is an array of delivery objects. The queued payload is not included. The plugin keeps delivered rows for 7 days and failed rows for 30 days.
bash
curl -u USER:APPLICATION_PASSWORD \
https://example.com/wp-json/link-manager-pro/v1/webhooks/31/deliveriesjson
{
"success": true,
"data": {
"data": [
{
"id": 904,
"webhook_id": 31,
"event": "link_clicked",
"status": "success",
"attempts": 1,
"response_code": 200,
"last_error": null,
"created_at": "2026-10-23 09:10:02",
"delivered_at": "2026-10-23 09:10:07"
}
]
}
}Errors:
| Status | When |
|---|---|
404 | No webhook has that ID. |
Reveal a webhook's secret
GET /webhooks/{id}/secret
Returns the raw signing secret of one webhook.
Permission: manage_options
Path parameters:
| Name | Type | What it is |
|---|---|---|
id | integer | The webhook ID. |
Response: 200. data holds:
| Field | Type | What it holds |
|---|---|---|
secret | string | The raw secret. Empty when none can be read. |
json
{
"success": true,
"data": { "secret": "Qz7hT0w3Lk9mN2pR5vX8cB1dF4gJ6sUyaa1b2" }
}Errors:
| Status | When |
|---|---|
404 | No webhook has that ID. |
Regenerate a webhook's secret
POST /webhooks/{id}/regenerate-secret
Replaces the signing secret with a new random one and returns it. The old secret stops working at once, so update your receiver.
Permission: manage_options
Path parameters:
| Name | Type | What it is |
|---|---|---|
id | integer | The webhook ID. |
Body parameters: None.
The new secret is 40 letters and digits. The webhook's failure count is reset to 0.
Response: 200, with the message "Signing secret regenerated." data is the webhook object plus the raw secret, as in Create a webhook.
json
{
"success": true,
"data": {
"id": 31,
"name": "Zapier clicks",
"target_url": "https://hooks.example.com/catch/123",
"events": ["link_clicked", "link_created"],
"status": "active",
"method": "POST",
"ignore_signing": "no",
"headers": { "X-Source": "lmp" },
"has_secret": "yes",
"secret_mask": "••••••••••••••••••••••••••••••••••••k3m9",
"secret": "Bn4wR8yT2xQ6cV0zL5jH9sD1fG3aP7uEoo2k3m9"
},
"message": "Signing secret regenerated."
}Errors:
| Status | When |
|---|---|
404 | No webhook has that ID. |
Send a test delivery
POST /webhooks/{id}/test
Sends one request to the webhook right now and returns how it went. The request has the event ping.
Permission: manage_options
Path parameters:
| Name | Type | What it is |
|---|---|---|
id | integer | The webhook ID. |
Body parameters: None.
The test goes out at once, signed like a real delivery, and does not use the queue. It is not stored, so it never shows in the deliveries list. It runs even when the webhook is paused. Its data holds message set to Link Manager Pro webhook test. The route answers 200 whether or not the receiver accepted the request. Read ok.
Response: 200. data holds:
| Field | Type | What it holds |
|---|---|---|
ok | boolean | true when the receiver answered with a status from 200 to 299. |
response_code | integer | The receiver's HTTP status. 0 when there was no answer or the address was blocked. |
error | string or null | null on success. Otherwise http_ and the status, a connection error text of up to 500 characters, or blocked_url (private/reserved host or non-https). |
live | boolean | Whether live events are sent. A test is sent either way. |
live_note | string | A short note when live is false. Empty otherwise. |
The message is "Test delivered successfully." when ok is true. When it is false, the message is "Test delivery failed. See the response code and error." A delivered test resets the webhook's failure count. The test is sent either way. live is false when this site doesn't queue live events, and live_note then says why.
bash
curl -X POST -u USER:APPLICATION_PASSWORD \
https://example.com/wp-json/link-manager-pro/v1/webhooks/31/testjson
{
"success": true,
"data": {
"ok": true,
"response_code": 200,
"error": null,
"live": true,
"live_note": ""
},
"message": "Test delivered successfully."
}Errors:
| Status | When |
|---|---|
404 | No webhook has that ID. |