Webhook Management
Manage your webhook endpoints to receive real-time event notifications. You can have up to 10 active webhooks per organization.
All webhook mutations (POST, PUT, DELETE, and the test endpoint) require an HMAC signature (see Authentication). Listing webhooks is API key only. Request bodies are strictly validated: unknown fields return 400.
Create Webhook
POST /webhooksRequest Body
{
"url": "https://yourapp.com/webhooks/vignetim",
"events": ["order.completed", "order.failed"],
"description": "Production order notifications"
}| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS endpoint URL to receive webhook deliveries |
events | string[] | Yes | 1-10 event types from: order.completed, order.fulfilled, order.failed, order.refunded, order.cancelled, * |
description | string | No | A human-readable description, max 255 chars |
Example Request
API_KEY="your-api-key"
API_SECRET="your-api-secret"
# Signing key = SHA-256 hex digest of your API secret
# $NF, not $2: OpenSSL prints "SHA2-256(stdin)= <hash>" but LibreSSL (the macOS
# default) prints the bare hash, where $2 is empty — that yields an empty signing
# key and a 401 with nothing to indicate why.
SIGNING_KEY=$(echo -n "${API_SECRET}" | openssl dgst -sha256 | awk '{print $NF}')
TIMESTAMP=$(date +%s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]')
METHOD="POST"
REQ_PATH="/v2/partners/webhooks"
# No request body: sign an empty body string.
BODY=""
MESSAGE="${TIMESTAMP}.${NONCE}.${METHOD}.${REQ_PATH}.${BODY}"
SIGNATURE=$(echo -n "${MESSAGE}" | \
openssl dgst -sha256 -hmac "${SIGNING_KEY}" | \
awk '{print $NF}')
curl -X POST "https://api.vignetim.com${REQ_PATH}" \
-H "X-API-Key: ${API_KEY}" \
-H "X-Timestamp: ${TIMESTAMP}" \
-H "X-Nonce: ${NONCE}" \
-H "X-Signature: ${SIGNATURE}"Response (201 Created)
{
"id": "1f2e3d4c-5b6a-7980-cdef-0123456789ab",
"url": "https://yourapp.com/webhooks/vignetim",
"events": ["order.completed", "order.failed"],
"active": true,
"description": "Production order notifications",
"failureCount": 0,
"createdAt": "2026-03-20T14:30:00.000Z",
"signingSecret": "<64-char-hex-signing-secret>"
}Important: The
signingSecret(a 64-char hex string) is only returned when the webhook is first created. Store it securely; you need it to verify incoming webhook signatures and it cannot be retrieved again.
Creating an 11th active webhook returns 400.
SSRF Protection
Webhook URLs must be publicly reachable HTTPS endpoints. Rejected with 400:
- Non-HTTPS URLs
- Localhost, loopback, and internal hostnames (
.local,.internal, cluster suffixes) - Hostnames resolving to private or link-local IP ranges (10.x, 172.16-31.x, 192.168.x, 169.254.x, CGNAT)
- Cloud metadata endpoints
List Webhooks
GET /webhooksAPI key only, no signature required.
Example Request
curl "https://api.vignetim.com/v2/partners/webhooks" \
-H "X-API-Key: $VIGNETIM_API_KEY"Response
{
"data": [
{
"id": "1f2e3d4c-5b6a-7980-cdef-0123456789ab",
"url": "https://yourapp.com/webhooks/vignetim",
"events": ["order.completed", "order.failed"],
"active": true,
"description": "Production order notifications",
"lastDeliveredAt": "2026-03-20T14:31:15.000Z",
"failureCount": 0,
"createdAt": "2026-03-20T14:30:00.000Z"
}
],
"metadata": {
"total": 1
}
}| Field | Type | Description |
|---|---|---|
id | UUID | Webhook ID |
url | string | Delivery URL |
events | string[] | Subscribed events |
active | boolean | Whether the webhook receives deliveries |
description | string | Description (optional) |
lastDeliveredAt | ISO 8601 | Timestamp of the last successful delivery (optional) |
failureCount | integer | Consecutive failed deliveries; resets to 0 on success |
createdAt | ISO 8601 | Creation timestamp |
Update Webhook
PUT /webhooks/:idPath Parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The webhook ID |
Request Body
All fields are optional; only provided fields are updated.
{
"url": "https://yourapp.com/webhooks/vignetim-v2",
"events": ["*"],
"active": true,
"description": "Updated to receive all events"
}| Field | Type | Description |
|---|---|---|
url | string | New HTTPS URL (re-validated against SSRF rules) |
events | string[] | New event subscription (max 10, same allowed values) |
active | boolean | Set true to re-enable an auto-disabled webhook, false to pause |
description | string | New description, max 255 chars |
Example Request
API_KEY="your-api-key"
API_SECRET="your-api-secret"
# Signing key = SHA-256 hex digest of your API secret
# $NF, not $2: OpenSSL prints "SHA2-256(stdin)= <hash>" but LibreSSL (the macOS
# default) prints the bare hash, where $2 is empty — that yields an empty signing
# key and a 401 with nothing to indicate why.
SIGNING_KEY=$(echo -n "${API_SECRET}" | openssl dgst -sha256 | awk '{print $NF}')
TIMESTAMP=$(date +%s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]')
METHOD="PUT"
REQ_PATH="/v2/partners/webhooks/1f2e3d4c-5b6a-7980-cdef-0123456789ab"
# No request body: sign an empty body string.
BODY=""
MESSAGE="${TIMESTAMP}.${NONCE}.${METHOD}.${REQ_PATH}.${BODY}"
SIGNATURE=$(echo -n "${MESSAGE}" | \
openssl dgst -sha256 -hmac "${SIGNING_KEY}" | \
awk '{print $NF}')
curl -X PUT "https://api.vignetim.com${REQ_PATH}" \
-H "X-API-Key: ${API_KEY}" \
-H "X-Timestamp: ${TIMESTAMP}" \
-H "X-Nonce: ${NONCE}" \
-H "X-Signature: ${SIGNATURE}"Response
Returns the updated webhook object (same shape as list items, without signingSecret).
Delete Webhook
DELETE /webhooks/:idExample Request
API_KEY="your-api-key"
API_SECRET="your-api-secret"
# Signing key = SHA-256 hex digest of your API secret
# $NF, not $2: OpenSSL prints "SHA2-256(stdin)= <hash>" but LibreSSL (the macOS
# default) prints the bare hash, where $2 is empty — that yields an empty signing
# key and a 401 with nothing to indicate why.
SIGNING_KEY=$(echo -n "${API_SECRET}" | openssl dgst -sha256 | awk '{print $NF}')
TIMESTAMP=$(date +%s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]')
METHOD="DELETE"
REQ_PATH="/v2/partners/webhooks/1f2e3d4c-5b6a-7980-cdef-0123456789ab"
# No request body: sign an empty body string.
BODY=""
MESSAGE="${TIMESTAMP}.${NONCE}.${METHOD}.${REQ_PATH}.${BODY}"
SIGNATURE=$(echo -n "${MESSAGE}" | \
openssl dgst -sha256 -hmac "${SIGNING_KEY}" | \
awk '{print $NF}')
curl -X DELETE "https://api.vignetim.com${REQ_PATH}" \
-H "X-API-Key: ${API_KEY}" \
-H "X-Timestamp: ${TIMESTAMP}" \
-H "X-Nonce: ${NONCE}" \
-H "X-Signature: ${SIGNATURE}"Response
{
"success": true
}Test Webhook
Send a test event to verify that your endpoint is reachable and processes deliveries correctly. The payload is documented in Webhooks Overview.
POST /webhooks/:id/testExample Request
The test endpoint takes no request body; sign an empty body string.
API_KEY="your-api-key"
API_SECRET="your-api-secret"
# Signing key = SHA-256 hex digest of your API secret
# $NF, not $2: OpenSSL prints "SHA2-256(stdin)= <hash>" but LibreSSL (the macOS
# default) prints the bare hash, where $2 is empty — that yields an empty signing
# key and a 401 with nothing to indicate why.
SIGNING_KEY=$(echo -n "${API_SECRET}" | openssl dgst -sha256 | awk '{print $NF}')
TIMESTAMP=$(date +%s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]')
METHOD="POST"
REQ_PATH="/v2/partners/webhooks/1f2e3d4c-5b6a-7980-cdef-0123456789ab/test"
# No request body: sign an empty body string.
BODY=""
MESSAGE="${TIMESTAMP}.${NONCE}.${METHOD}.${REQ_PATH}.${BODY}"
SIGNATURE=$(echo -n "${MESSAGE}" | \
openssl dgst -sha256 -hmac "${SIGNING_KEY}" | \
awk '{print $NF}')
curl -X POST "https://api.vignetim.com${REQ_PATH}" \
-H "X-API-Key: ${API_KEY}" \
-H "X-Timestamp: ${TIMESTAMP}" \
-H "X-Nonce: ${NONCE}" \
-H "X-Signature: ${SIGNATURE}"Response
On a successful delivery:
{
"success": true,
"statusCode": 200
}If the delivery fails:
{
"success": false,
"statusCode": 500
}If the request could not be completed at all (timeout, DNS failure), statusCode is replaced by an error message:
{
"success": false,
"error": "The operation was aborted due to timeout"
}