AccessLayer supports trade webhooks to notify external creators and integrations in real time when key trades (buys or sells) occur for a creator profile.
To register, view, or delete webhooks, creators must authenticate using Stellar wallet signature headers.
All management requests require the following headers for wallet verification:
x-wallet-address: The Stellar public key (G...) of the creator's wallet.x-signature: A Base64-encoded signature of the request payload, verifying wallet ownership.x-timestamp: The Unix timestamp (in milliseconds) when the signature was generated. The signature is rejected if this timestamp is older than 5 minutes.
- Method:
POST - Path:
/api/v1/creators/:id/webhooks - Request Body (JSON):
{ "callback_url": "https://your-domain.com/webhooks/trade-handler", "events": ["buy", "sell"] } - Response (201 Created):
{ "success": true, "data": { "id": "cm1a2b3c4d0000z9y8x7w6v5u4", "creatorId": "webhook-test-creator-id", "callbackUrl": "https://your-domain.com/webhooks/trade-handler", "events": ["buy", "sell"], "isActive": true, "isFailing": false, "createdAt": "2026-06-23T04:00:00.000Z", "updatedAt": "2026-06-23T04:00:00.000Z" }, "message": "Webhook registered successfully" }
- Method:
GET - Path:
/api/v1/creators/:id/webhooks - Response (200 OK):
{ "success": true, "data": [ { "id": "cm1a2b3c4d0000z9y8x7w6v5u4", "creatorId": "webhook-test-creator-id", "callbackUrl": "https://your-domain.com/webhooks/trade-handler", "events": ["buy", "sell"], "isActive": true, "isFailing": false, "createdAt": "2026-06-23T04:00:00.000Z", "updatedAt": "2026-06-23T04:00:00.000Z" } ] }
- Method:
DELETE - Path:
/api/v1/creators/:id/webhooks/:webhookId - Response (200 OK):
{ "success": true, "data": { "id": "cm1a2b3c4d0000z9y8x7w6v5u4" }, "message": "Webhook deleted successfully" }
When a trade occurs, AccessLayer sends an HTTP POST request containing a JSON body to the registered callback_url.
| Field Name | Type | Description | Example Value |
|---|---|---|---|
event_type |
string |
The type of trade event ("buy" or "sell"). |
"buy" |
creator_id |
string |
The Stellar public key or identifier of the creator whose keys were traded. | "GCSW65D4G56DF...2XDF" |
buyer_or_seller_address |
string |
The Stellar public key of the trader's wallet executing the transaction. | "GDD3DDK4J5H5D...8LKF" |
amount |
string |
The amount of keys traded (decimal representation). | "1.0000000" |
price |
string |
The price per key in XLM (decimal representation). | "15.5000000" |
fee_paid |
string |
The protocol/creator fee paid for this trade in XLM. | "0.4650000" |
timestamp |
string |
The ISO 8601 UTC timestamp of the trade event transaction. | "2026-06-23T04:00:00.000Z" |
{
"event_type": "buy",
"creator_id": "GCSW65D4G56DF8B2N7M9L3K4J2XDF",
"buyer_or_seller_address": "GDD3DDK4J5H5D9S8A7P6O5I4U8LKF",
"amount": "100.0000000",
"price": "10.5000000",
"fee_paid": "0.5000000",
"timestamp": "2026-06-23T04:00:00.000Z"
}Integrators should be aware of the following delivery characteristics when handling webhooks:
AccessLayer guarantees that all matching trade events are delivered at least once to your callback URL. However, under certain conditions (such as network hiccups, database latency, or retries), the same event might be sent multiple times.
Tip
Idempotency Handling: Webhook consumers should check if an event has already been processed before taking action. Since event payloads do not currently include a unique event UUID, consumers can construct an idempotency key using a hash or combination of timestamp, buyer_or_seller_address, amount, and price.
Because webhook deliveries are handled asynchronously and retry delays can occur on a per-event basis, delivery order is not strictly guaranteed.
Important
Event Chronology: Consumers should inspect the timestamp field in the webhook payload to determine the actual chronological sequence of trade events, rather than relying on the order of HTTP request arrivals.
If a delivery attempt fails, AccessLayer retries the delivery using an exponential backoff schedule.
- Request Timeout: Each delivery request attempt has a hard timeout of 5 seconds.
- Maximum Attempts: AccessLayer will attempt delivery up to 3 times (the original dispatch plus 2 retries).
-
Exponential Backoff: The delay before retrying increases exponentially with each failed attempt, using the formula:
$$\text{delay (ms)} = 2^{\text{attempt}} \times 1000$$ - Attempt 1 (Original): Dispatched immediately.
- Attempt 2 (Retry 1): Delays 2 seconds after Attempt 1 fails.
- Attempt 3 (Retry 2): Delays 4 seconds after Attempt 2 fails.
-
Exhaustion & Failure Flagging:
- If all 3 attempts fail, the event status is updated to
FAILEDin the database, and the error description is stored inlastError. - The parent webhook registration is updated with
isFailing = true. - Suspension: While a webhook is flagged as failing, future events will not be dispatched to it. This prevents unnecessary traffic to dead endpoints. Creators must delete and recreate the webhook (or update its status once the endpoint is resolved) to resume dispatches.
- If all 3 attempts fail, the event status is updated to
Creators should verify every incoming webhook before processing the payload. Verification confirms that the request was produced by AccessLayer and that the request body was not modified in transit.
AccessLayer includes the following headers on each outgoing webhook POST request:
| Header | Description |
|---|---|
x-accesslayer-signature |
Hex-encoded HMAC digest for the request. |
x-accesslayer-timestamp |
Unix timestamp in milliseconds when AccessLayer generated the signature. |
AccessLayer computes the signature with HMAC-SHA256 using the webhook signing secret associated with the webhook registration.
The signed message is the exact timestamp header value, a period (.), and the exact raw HTTP request body bytes:
<x-accesslayer-timestamp>.<raw-request-body>
For example, if the timestamp header is 1782705600000, the HMAC input is:
1782705600000.{"event_type":"buy","creator_id":"GCSW...","amount":"100.0000000"}
Important
Use the raw request body exactly as received on the wire. Do not parse and re-serialize JSON before verification, because whitespace or property-order changes will produce a different HMAC.
- Read
x-accesslayer-signatureandx-accesslayer-timestampfrom the request headers. - Reject the request if either header is missing.
- Parse
x-accesslayer-timestampas a Unix timestamp in milliseconds. - Reject the request if the timestamp is outside the allowed replay window. AccessLayer recommends accepting signatures only when the timestamp is within 5 minutes of your server time.
- Build the signed message as
timestamp + "." + rawBody. - Compute
HMAC-SHA256(signedMessage, webhookSigningSecret)and encode the digest as lowercase hexadecimal. - Compare the computed digest with
x-accesslayer-signatureusing a constant-time comparison function. - Process the webhook only after both the timestamp and signature checks pass.
function handleAccessLayerWebhook(request):
signature = request.header("x-accesslayer-signature")
timestamp = request.header("x-accesslayer-timestamp")
rawBody = request.rawBody
if signature is missing or timestamp is missing:
return response(status = 400)
timestampMs = parseInteger(timestamp)
nowMs = currentUnixTimeMilliseconds()
if timestampMs is invalid:
return response(status = 400)
if absoluteValue(nowMs - timestampMs) > 5 minutes:
return response(status = 400)
signedMessage = timestamp + "." + rawBody
expectedSignature = hex(hmacSha256(secret = webhookSigningSecret, message = signedMessage))
if not constantTimeEquals(expectedSignature, signature):
return response(status = 400)
processWebhook(JSON.parse(rawBody))
return response(status = 200)
The timestamp header is part of the signed message, so a third party cannot change it without invalidating the signature. Consumers should reject any request whose x-accesslayer-timestamp is more than 5 minutes in the past or future relative to their server clock. This limits the time window in which a captured valid request can be replayed.
For additional protection, consumers may store recently seen signatures or idempotency keys for the same 5-minute window and reject duplicates. This duplicate check is optional but recommended for high-risk integrations.
If any verification step fails, return HTTP 400 Bad Request and do not process the payload. Do not partially process failed requests, enqueue background work for them, or treat them as successful deliveries.