Method POST/partner/webhooks

Register an HTTPS address that will receive the events of your brand, and choose the event types sent to that address. Scope webhooks:write.

On this page

You register an HTTPS address that will receive the events of your brand, and you choose the event types sent to that address. The subscription belongs to the brand of the key that calls. By the end of this page, you will know which event types actually go out today, how to verify the signature of a delivery, and which response your integration receives at every refusal.

Full address:

HTTP
POST https://api.sealtrust.io/v1/partner/webhooks

The same endpoint also answers without the /v1 prefix, at https://api.sealtrust.io/partner/webhooks. The two addresses call the same code and both are permanent. Use the /v1 form for a new integration.

#Authorization

API key in the Authorization header, in the Bearer format followed by a space then the key.

What is requiredValue
AuthenticationAPI key of your brand
Scope carried by the keywebhooks:write
Feature of your planwebhook notifications, included from the Prestige plan upward, absent from the Essentiel plan

The Prestige, Maison and Inside plans carry webhook notifications. The Essentiel plan does not carry them, and neither does the trial account. If your plan does not carry them, you receive a 403 on this route.

The key decides the brand. You cannot create a subscription for a brand other than its own, and no field of the body allows changing it.

A missing scope returns 403 without creating anything.

#Rate limit

We measure the rate over a fixed 60-second window. The cap depends on your plan, and we can raise it for your brand without changing your subscription. Read the current value in the X-RateLimit-Limit header of every accepted response. When no value is set on your brand or on your plan, the fallback value is 120 calls per window.

Two counters are layered, with the same cap: one per key, one for the sum of all the keys of your brand. Creating one more key therefore does not increase the total rate allowed.

HeaderContent
X-RateLimit-Limitthe cap applied over the window
X-RateLimit-Remainingwhat is left to you in the current window
X-RateLimit-Resetthe end-of-window timestamp, in seconds
X-RateLimit-Scopekey or brand, the more constraining of the two counters

Exceeding it returns 429 with these four headers, plus Retry-After. Retry-After counts the seconds that remain in the current window, and is at minimum 1.

#Path and query parameters

None. This endpoint reads neither path parameter, nor query parameter. Everything is in the body.

#Request body

Content-Type application/json.

NameTypeRequiredDescription
urlstringyesThe address that will receive the events. It must start with https:// and be between 10 and 2048 characters long.
eventsstring[]noThe event types you subscribe to. Each value must appear in the list below. Absent or empty, the subscription never receives anything.
secretstringnoThe shared secret that signs every delivery. 128 characters at most. Absent, the deliveries go out without a signature.

The body accepts only these three names. Any other field makes the request fail with a 422.

#The 23 accepted event types

A name absent from these two lists makes the request fail with a 422.

#The 17 types we deliver today

Life cycle of an item

product.minted, product.transferred, transfer.accepted

Scans and security

product.scanned, product.gray_market, clone.alert

Returns and warranty

return.requested, return.received, return.completed, return.rejected, return.expired, warranty.claimed

Buyback

buyback.offered, buyback.accepted, buyback.declined, buyback.completed, buyback.expired

#The 6 types accepted at subscription and never emitted

product.burned, product.status_changed, batch.completed, batch.failed, certificate.issued, warranty.expiring_soon

These six names pass input validation and are recorded in your subscription. No code in the product emits them to this day, so your address will never receive a delivery carrying one of these names. Build no alert and no automation on them. We will update this list the day one of them goes out.

#What the secret does

When you supply a secret, every delivery carries the header X-Webhook-Signature, in the format t=<horodatage>,v1=<empreinte>. The hash is an HMAC-SHA256 computed with your secret over the string made of the timestamp, a dot, then the body received byte for byte. Verify the signature on the raw bytes you receive, before decoding the JSON.

Three other headers come with every delivery.

HeaderContentCovered by the signature
X-Webhook-Eventthe event type delivered, for example product.scannedno
X-Webhook-Ida stable identifier for a given event, which you use to discard duplicates from resendsno
X-Webhook-Timestampthe timestamp used in the signature, in secondsyes

X-Webhook-Event tells you which event it is when a subscription covers several types. It stays outside the signature, so it authenticates nothing. When your receiver must route on an authenticated value, register one address per event type, and use this header only to read your logs.

Without a secret, the X-Webhook-Signature header is absent from your deliveries.

#Example request

The three programs do the same thing: they register one address for two event types that are actually delivered, with a signing secret.

curl -X POST https://api.sealtrust.io/v1/partner/webhooks \
  -H "Authorization: Bearer st_test_0000000000000000000000000000000000000000000000" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://exemple-sas.example.com/sealtrust/evenements",
    "events": ["product.scanned", "clone.alert"],
    "secret": "secret-partage-a-remplacer"
  }'

#Example response

A successful creation answers 201 Created.

201 Created
{
  "id": 41,
  "brand_id": 12,
  "url": "https://exemple-sas.example.com/sealtrust/evenements",
  "events": ["product.scanned", "clone.alert"],
  "is_active": true,
  "health": "healthy",
  "created_at": "2026-08-20T09:14:03.512841+00:00",
  "updated_at": "2026-08-20T09:14:03.512841+00:00"
}
FieldTypeWhat it contains
idintegerThe identifier of the subscription. It is the one you will pass to the read, modify and delete routes.
brand_idintegerThe owning brand, the one of your key.
urlstringThe address registered. It is this exact value that the deletion will ask you to copy back.
eventsstring[]The event types subscribed to.
is_activebooleantrue at creation. A modification can switch it to false to turn the subscription off without deleting it.
healthstringhealthy at creation. Switches to degraded when the series of resends gives up on your address, and comes back to healthy at the first successful delivery.
created_atstringDate and time of creation, in ISO 8601 format.
updated_atstringDate and time of the last modification, in ISO 8601 format.

#Errors

The body of an error response contains a single field, detail.

CodeConditionWhat you must do
401The Authorization header is missing. detail is "Missing Authorization header", and the response carries WWW-Authenticate: Bearer.Add the Authorization: Bearer <votre clef> header.
401The header does not start with Bearer followed by a space. detail is "Invalid Authorization header format (expected 'Bearer <token>')", and the response carries WWW-Authenticate: Bearer.Respect the word Bearer, a space, then the key.
401The value sent is empty or is shorter than 40 characters. detail is "Invalid API key format".Check that the key was copied in full.
401The key matches no known key. detail is "Invalid API key".The key is wrong or was deleted. Create one from the console of your brand.
403The key is no longer active. detail repeats its state, for example "API key is revoked".Use an active key.
403The key has passed its expiration date. detail is "API key has expired".Create a new key. The key switches to expired as of this refusal.
403The key does not carry the required scope. detail is "Missing required scope: webhooks:write".Create a key that carries this scope.
403Your plan does not include notifications. detail is an object whose code is FEATURE_NOT_AVAILABLE and whose feature is webhooks.Move to a plan that carries them. Replaying the request changes nothing.
422Validation refuses the body when: url is missing, url does not start with https://, url falls outside the bounds of 10 to 2048 characters, an event type is unknown, secret exceeds 128 characters, or an unknown field is present. detail is a list, and each entry carries loc, type and msg.Read loc to know which value is refused, correct it, call again.
429Your key exceeds its rate cap. detail starts with Rate limit exceeded, and X-RateLimit-Scope is key.Wait the number of seconds given by Retry-After, then call again.
429The sum of the keys of your brand exceeds the cap. detail starts with Brand rate limit exceeded, and X-RateLimit-Scope is brand.Wait Retry-After. Creating one more key does not increase this cap.
503The service that keeps the rate counters is momentarily unavailable. detail is "Rate limiting temporarily unavailable, please retry shortly".We created nothing. Try again later.
500A failure on our side. detail is "Internal Server Error", or "Internal server error" when the failure occurs while reading your key.Try again. If the refusal persists, send us the X-Request-Id header of the response.

#See also

Your answer opens a pre-filled email in your mail app, addressed to contact@sealtrust.io. You read it over before sending it.

Suggest a correctionReport a problem