Method PUT/partner /webhooks /{webhook_id}
Modify a notification subscription: its destination URL, its event list, its signing secret, whether it is active. Scope webhooks:write.
On this page
You modify a notification subscription that your brand already owns, and you receive the subscription in its new state.
#
Send your API key in the Authorization header, in the form Bearer followed
by a space then the key. The key must carry the webhooks:write scope. A key
that does not carry this scope receives a 403 whose message names the missing
scope.
| What we require | Value |
|---|---|
| Authentication | API key of your brand |
| Scope carried by the key | webhooks:write |
| Feature of your plan | webhook notifications |
Two conditions come on top of the scope of the key.
- The subscription must belong to the brand of the key. We look for the subscription by its number and by your brand. When that lookup finds nothing, you receive a 404. A subscription that belongs to another brand gives exactly the same response. This endpoint therefore never tells you that a number exists elsewhere.
- The plan of your brand must include notifications. Otherwise you receive a
403 carrying the
FEATURE_NOT_AVAILABLEcode. One exception exists, described just below.
The full URL of this endpoint is
https://api.sealtrust.io/v1/partner/webhooks/{webhook_id}. The same endpoint
also answers without the /v1 prefix, at
https://api.sealtrust.io/partner/webhooks/{webhook_id}. Both URLs call the
same code. Use the /v1 form for a new integration.
#Rate limit
We measure the rate over a fixed 60 second window. The ceiling rises with your plan, and we can set a value on your brand that wins over the one of your plan. We therefore read, in this order: the value set on your brand, then the one of your plan, then a fallback value of 120 calls per window. That fallback value is used only if your brand and your plan carry none.
Do not guess your ceiling of the moment. You read it on every response that gets past the rate check.
Two counters overlap, with the same ceiling: one per key, one for the sum of all the keys of your brand. Creating extra keys therefore does not raise the total rate allowed.
Four headers describe the more constraining of the two counters.
| Header | Content |
|---|---|
X-RateLimit-Limit | the ceiling applied over the window |
X-RateLimit-Remaining | what is left to you in the current window |
X-RateLimit-Reset | the end timestamp of the window, in seconds |
X-RateLimit-Scope | key or brand, the counter that served as the reference |
A rate refusal returns 429 with Retry-After, expressed in seconds remaining
in the current window. That value is never below 1.
This endpoint consumes no daily quota of a key and no monthly quota of a plan. The rate ceiling is the only counter that applies here.
#Path and query parameters
| Name | Type | Required | Description |
|---|---|---|---|
webhook_id | integer | yes | The number of the subscription to modify, in the path. A value that is not an integer gives a 422. |
This endpoint has no query parameter.
#Headers
| Name | Type | Required | Description |
|---|---|---|---|
Authorization | string | yes | Bearer followed by a space then your API key. |
Content-Type | string | yes | application/json. |
Idempotency-Key | string | no | This endpoint does not declare this header. It neither reads it nor remembers it. |
#Request body
The body is a JSON object. The four fields are optional. Send only the fields you want to change. A field absent from the body keeps its value.
| Name | Type | Required | Description |
|---|---|---|---|
url | string | no | The new destination URL, from 10 to 2048 characters. It must start with https://. Never send null in this field, read the warning below. |
events | array of string | no | The new list of event types. It replaces the old list entirely. Every value must belong to the list below. |
is_active | boolean | no | true to receive the events, false to stop the deliveries without destroying the subscription. This field accepts true or false. Omit it if you do not want to change it. |
secret | string | no | The new signing secret, up to 128 characters. We use it to compute the X-Webhook-Signature header of every delivery. null erases the secret, and the following deliveries then go out without a signature. |
The server refuses any field that this table does not name. An unknown field makes the request fail with a 422, and we modify nothing.
An empty body, written {}, is valid. It modifies nothing, it returns the
subscription unchanged, and we require the plan as for any other modification.
#The 23 event types
A value of events that is not in this list makes the request fail with a 422,
and the message names the rejected values.
| Family | Types |
|---|---|
| Product lifecycle | product.minted, product.transferred, product.burned, product.status_changed |
| Batches | batch.completed, batch.failed |
| Certificate | certificate.issued |
| Distribution and security | product.scanned, product.gray_market, clone.alert, transfer.accepted |
| Returns | return.requested, return.received, return.completed, return.rejected, return.expired |
| Warranty | warranty.claimed, warranty.expiring_soon |
| Buyback | buyback.offered, buyback.accepted, buyback.declined, buyback.completed, buyback.expired |
#Example request
You repoint subscription number 128 to a new URL and you cut its list down to
two event types.
curl -i -X PUT https://api.sealtrust.io/v1/partner/webhooks/128 \
-H "Authorization: Bearer st_test_0000000000000000000000000000000000000000000000" \
-H "Content-Type: application/json" \
-d '{
"url": "https://exemple-sas.example.com/sealtrust/evenements",
"events": ["product.minted", "batch.completed"]
}'import { SealTrustClient } from "@sealtrust-io/sdk";
const sealtrust = new SealTrustClient({
apiKey: "st_test_0000000000000000000000000000000000000000000000",
});
const abonnement = await sealtrust.webhooks.update(128, {
url: "https://exemple-sas.example.com/sealtrust/evenements",
events: ["product.minted", "batch.completed"],
});
console.log(abonnement.id, abonnement.url, abonnement.events);import requests
response = requests.put(
"https://api.sealtrust.io/v1/partner/webhooks/128",
headers={
"Authorization": "Bearer st_test_0000000000000000000000000000000000000000000000",
},
json={
"url": "https://exemple-sas.example.com/sealtrust/evenements",
"events": ["product.minted", "batch.completed"],
},
timeout=30,
)
print(response.status_code)
print(response.headers.get("X-RateLimit-Remaining"))
print(response.json())#Example response
HTTP code 200.
{
"id": 128,
"brand_id": 12,
"url": "https://exemple-sas.example.com/sealtrust/evenements",
"events": ["product.minted", "batch.completed"],
"is_active": true,
"health": "healthy",
"created_at": "2026-08-18T09:12:44.512038+00:00",
"updated_at": "2026-08-20T14:03:21.884517+00:00"
}The response has eight fields and nothing else. The secret never appears in it.
| Field | Type | Description |
|---|---|---|
id | integer | The number of the subscription. |
brand_id | integer | The number of your brand. |
url | string | The destination URL recorded. Read it again after every modification. |
events | array of string | The list of event types recorded. |
is_active | boolean | true if the subscription receives the events. |
health | string | healthy or degraded. It is degraded after the definitive failure of a delivery, and comes back to healthy on the first successful delivery. |
created_at | string | The creation date of the subscription, in Universal Time. |
updated_at | string | The date of the last modification, in Universal Time. |
#Errors
The body of an error response carries a detail field. Depending on the case,
that field contains a sentence, a list or an object.
| Code | Condition | What to do |
|---|---|---|
| 401 | The Authorization header is missing. detail is Missing Authorization header. The response also carries WWW-Authenticate: Bearer. | Add the header. |
| 401 | The Authorization header does not start with Bearer followed by a space. The response also carries WWW-Authenticate: Bearer. | Fix the form of the header. |
| 401 | The key sent is empty, or shorter than 40 characters. detail is Invalid API key format. | Send the whole key. A key truncated on screen gives this response. |
| 401 | The key sent is unknown. detail is Invalid API key. | Check that you are sending the complete key, with no space and no line break. |
| 403 | The key is no longer active, because it was revoked. detail names its state. | Create a new key in the console. |
| 403 | The key has reached its expiry date. detail is API key has expired. | Create a new key. The old one will never become valid again. |
| 403 | The key does not carry the webhooks:write scope. detail is Missing required scope: webhooks:write. | Create a key carrying this scope. The scopes of an existing key cannot be modified. |
| 403 | The plan of your brand does not include notifications. detail is {"code": "FEATURE_NOT_AVAILABLE", "feature": "webhooks"}. | Change plan from the console, or contact us. You can still switch the subscription off with a body reduced to {"is_active": false}. |
| 404 | No subscription carries this number for your brand. detail is Webhook subscription not found. | Check the number with GET /v1/partner/webhooks. You receive the same response if the subscription belongs to another brand. |
| 422 | The webhook_id of the path is not an integer. | Send the number returned by GET /v1/partner/webhooks. |
| 422 | The webhook_id of the path is an integer too large to be a subscription number. The message speaks of a query parameter, whereas the offending value is in the path. | Send the number returned by GET /v1/partner/webhooks. |
| 422 | The body contains a field that this endpoint does not know. | Remove that field. We accept only url, events, is_active and secret. |
| 422 | url is a string that does not start with https://. | Fix the URL so that it starts with https://. |
| 422 | url is a string of fewer than 10 or more than 2048 characters. | Fix the length of the URL. |
| 422 | events contains at least one unknown type. The message names the rejected values. | Take the values from the table of the 23 event types. |
| 422 | secret exceeds 128 characters. | Shorten the secret. |
| 422 | A field does not carry the announced type, for example url receives a number or is_active receives a string. | Fix the type. The message names the offending field. |
| 422 | The body is not a JSON object, or the body is missing. | Send a JSON object, even an empty one, with Content-Type: application/json. |
| 429 | The rate ceiling of the key is reached. The Retry-After header and the X-RateLimit-* family accompany the response, with X-RateLimit-Scope: key. | Wait the number of seconds given by Retry-After, then try again. |
| 429 | The rate ceiling of the brand is reached, across all keys. X-RateLimit-Scope is brand. | Wait the number of seconds given by Retry-After. Creating an extra key does not raise this ceiling. |
| 500 | An unexpected error occurred. detail is Internal Server Error. | Try again. If the error persists, contact support giving the X-Request-Id header of the response. |
| 503 | The call rate limiting service is momentarily unavailable. We refuse the call before any read. | Try again in a few moments. The subscription was not modified. |
#See also
GET /partner/webhooks/{webhook_id}, read a subscription and its delivery state.GET /partner/webhooks, list your notification subscriptions, page by page.DELETE /partner/webhooks/{webhook_id}, delete a subscription and the signing secret attached to it.- Receiving events by webhook, create a subscription, verify a signature, catch up on lost events.
Was this page helpful?
Your answer opens a pre-filled email in your mail app, addressed to contact@sealtrust.io. You read it over before sending it.