Subscriptions allow external systems to receive queued webhook notifications when product data changes. Each subscription belongs to the authenticated organization and selects one or more supported product events:
product.createdproduct.updatedproduct.deleted
The API only dispatches an event to active subscriptions whose event_type contains that exact event name.
GET /api/v1/resources/subscriptions
Returns the subscriptions that belong to the current authenticated organization. The response can contain both active and inactive subscriptions.
Response example
{
"count": 1,
"subscriptions": [
{
"id": "f2b8e9d3-31c5-4a77-b814-f447bbab9fd2",
"resource_type": "subscription",
"url": "/api/v1/resources/subscriptions/f2b8e9d3-31c5-4a77-b814-f447bbab9fd2",
"created_date": 1735689600,
"start_date": 1735689600,
"end_date": null,
"consumer_id": "did:ishare:EU.NL.NTRNL-73085065",
"provider_id": "did:ishare:EU.NL.NTRNL-73085065",
"description": null,
"event_type": ["product.created", "product.updated"],
"status": "active",
"webhook_url": "https://example.com/webhooks/receive",
"signing_secret": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
]
}created_date, start_date, and end_date are Unix timestamps in subscription responses.
POST /api/v1/resources/subscriptions
Creates an active subscription and generates a signing secret.
Request body
{
"resource_type": "subscription",
"webhook_url": "https://example.com/webhooks/receive",
"event_type": ["product.created", "product.updated"]
}| Field | Type | Description |
|---|---|---|
resource_type | string | Required. Must be subscription. |
webhook_url | string | Required. A valid HTTP or HTTPS URL that receives webhook POST requests. HTTPS is strongly recommended. |
event_type | array | Required. One or more of product.created, product.updated, or product.deleted. |
start_date | datetime (optional) | When the subscription becomes active. Defaults to the creation time. |
end_date | datetime (optional) | When the subscription expires. |
Response: 201 Created
The response uses the subscription resource shape shown above. It includes a newly generated 64-character hexadecimal signing_secret. Store that secret securely and use it to verify webhook requests.
GET /api/v1/resources/subscriptions/{subscription}
Returns one subscription by UUID when it belongs to the current authenticated organization. Returns 404 Not Found otherwise.
POST /api/v1/resources/subscriptions/{subscription}/test
Queues a test delivery to the configured webhook_url and returns 202 Accepted. The request uses the normal notification envelope, with event_type set to test and event_data set to "[]".
test is not a subscribable event type and must not be included in event_type when creating a subscription.
DELETE /api/v1/resources/subscriptions/{subscription}
Deletes a subscription that belongs to the current authenticated organization and returns 200 OK. Future matching events are no longer delivered to it.
Webhook deliveries are queued HTTP POST requests with a JSON body. A product event is delivered only when:
- the product event is eligible for webhook delivery;
- the organization has API access;
- the subscription is active; and
- the subscription contains the exact event name.
Content-Type: application/json
Signature: <64-character lowercase hexadecimal HMAC>Signature is an HMAC-SHA256 digest of the raw JSON request body, calculated with the subscription's signing_secret. The signature has no sha256= prefix and is not included in the JSON body.
The delivery does not currently include a timestamp header.
{
"id": "d35bfca68771a51c842d5a609a93a4262f276ab153737fcb6efbeb3539a14017",
"resource_type": "notification",
"date": "2026-09-10T12:00:00+00:00",
"consumer_id": "EU.NL.NTRNL-12345678",
"provider_id": "EU.NL.NTRNL-73085065",
"subscription_id": "f2b8e9d3-31c5-4a77-b814-f447bbab9fd2",
"description": "Notification for product.updated",
"event": [
{
"id": "b43c67be-a846-4ac0-88f0-3ad9fb07f1dd",
"resource_type": "event",
"event_type": "product.updated",
"date": "2026-09-10T12:00:00+00:00",
"description": "Event: product.updated",
"event_data": "{\"product\":{\"id\":12345,\"websiteURI\":\"https://example.com/products/12345\",\"projectNumber\":678}}"
}
]
}The event field is an array. Each event's event_data is itself a JSON-encoded string, not a nested JSON object. Decode the request body first, then decode event[0].event_data to access the product payload. The abbreviated event_data example above shows the encoding; actual product deliveries contain the complete product resource.
Always calculate the signature from the unmodified raw request body. Do not decode and re-encode the body before verification because differences in JSON encoding change the digest.
$rawBody = file_get_contents('php://input');
$receivedSignature = $_SERVER['HTTP_SIGNATURE'] ?? '';
$expectedSignature = hash_hmac('sha256', $rawBody, $signingSecret);
if (!hash_equals($expectedSignature, $receivedSignature)) {
http_response_code(401);
exit;
}Only process the payload after hash_equals succeeds.
A delivery succeeds when the receiver responds with any 2xx status code within 10 seconds. A non-2xx response, connection failure, or timeout is treated as a failed attempt.
Failed deliveries are retried automatically by the API. Each delivery has at most three attempts in total, using exponential backoff between attempts. Receivers do not need to retry webhook delivery themselves.
Because a request can be delivered more than once, process notifications idempotently. The notification id remains part of the queued payload across retry attempts and can be used as an idempotency key.
- Verify the
Signatureheader against the raw request body with HMAC-SHA256 and the subscription'ssigning_secret. - Compare signatures with a timing-safe function such as
hash_equals. - Keep the signing secret confidential. Do not log it or expose it to client-side code.
- Use HTTPS in production. HTTP URLs are accepted, but they do not protect the request or signature from network interception.
- Reject invalid signatures before parsing or processing
event_data. - The API does not currently send a signed timestamp. Use the notification
idfor duplicate detection and apply receiver-side controls appropriate to your replay-risk model.
- Create a subscription with a webhook URL and one or more fully qualified product event names.
- Store the returned subscription UUID and signing secret securely.
- Receive the queued JSON notification at the configured URL.
- Verify the
Signatureheader against the raw request body. - Decode the body, then separately decode each event's
event_datastring. - Process the notification idempotently and return a
2xxresponse within 10 seconds. - Use the test endpoint to verify connectivity and signature handling.
- Delete the subscription when it is no longer needed.
- Return a
2xxresponse as soon as the webhook has been accepted; move slow processing to your own queue. - Treat notification IDs as idempotency keys because automatic retries can produce duplicate deliveries.
- Match event names exactly:
product.created,product.updated, andproduct.deleted. - Monitor non-
2xxresponses and timeouts on the receiving endpoint. - Keep the subscription UUID so the subscription can be retrieved, tested, or deleted later.
- Keep the signing secret in server-side secret storage.