Skip to main content

Webhooks

Webhooks allow you to receive real-time event notifications from AXRO (such as order updates, shipment notifications, etc.) by subscribing an HTTPS endpoint. When events occur, AXRO will send an HTTP POST request to your configured endpoint with a signed JSON payload.

Overview​

Authentication: All webhook subscription management endpoints require Authorization: Bearer YOUR_ACCESS_TOKEN.

Security: Each subscription includes an API token that:

  • Is used as the HMAC secret to sign the request body
  • Should be validated on your endpoint to verify authenticity

Delivery Semantics:

  • Timeout: Each attempt waits up to 10 seconds for your response
  • Success: Any 2xx HTTP response is considered successful
  • Retries on temporary failures: If your endpoint does not answer (timeout, connection refused) or answers with 500-599, 408 or 429, delivery is repeated up to three times, after roughly 1, 5 and 15 minutes. Use the X-Event-ID header to recognise a repeated event.
  • No retries on rejection: Any other 4xx answer, such as 400, 401 or 404, is taken as a decision rather than a hiccup. The event is not sent again, because the same request would be refused again.
  • After the last attempt: The event is set aside on our side and not delivered later. It is not lost silently, but you should not count on receiving it.
  • One hour to live: An event that has not been picked up for delivery within an hour is discarded instead of being sent late. This applies while an event is still waiting to be sent, which is the case during a longer outage on our side. A status change that old has usually been overtaken by the next one, so delivering it would tell you something that is no longer true.

Signature Verification: Each webhook includes:

  • X-Signature-Algorithm: Always HMAC-SHA256
  • X-Signature-Timestamp: Unix timestamp (seconds) when the signature was generated
  • X-Signature: Format is sha256={hex} where hex is the HMAC-SHA256 signature of "{timestamp}.{raw_body_bytes}" using your API token as the secret

Create or Update Webhook Subscription​

POST/api/v1/webhook/subscribe/

Create or update the webhook subscription for your customer account. If a subscription already exists, it will be updated with the new endpoint URL and token.

JSON body parameters

NameTypeRequiredDescription
endpoint_urlstring✅Your webhook endpoint URL. Must be https:// or http://localhost (for testing)
api_tokenstring✅Token used for authentication and HMAC signature verification

URL Validation:

  • https:// is allowed for any domain
  • http:// is only allowed for localhost, 127.0.0.1, or 0.0.0.0
  • Invalid URLs return HTTP 422 Unprocessable Entity

Example Request​

curl -X POST "https://api.axro.com/api/v1/webhook/subscribe/" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"endpoint_url": "https://webhooks.example.com/axro",
"api_token": "your_secure_webhook_token_here"
}'

Example Response​

{
"customer_number": "123456",
"endpoint_url": "https://webhooks.example.com/axro",
"active": true,
"token_tail": "_here"
}

Response Fields:

  • customer_number: Your customer number
  • endpoint_url: The subscribed webhook URL
  • active: Whether the subscription is active
  • token_tail: Last 4 characters of the API token (for verification)

Get Webhook Subscription​

GET/api/v1/webhook/subscription/

Retrieve the current webhook subscription for your customer account.

Example Request​

curl -X GET "https://api.axro.com/api/v1/webhook/subscription/" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Example Response​

{
"customer_number": "123456",
"endpoint_url": "https://webhooks.example.com/axro",
"active": true,
"token_tail": null
}

Note: The token_tail field is null when retrieving a subscription (only shown when creating/updating). Returns HTTP 404 if no subscription exists.


Delete Webhook Subscription​

DELETE/api/v1/webhook/subscription/

Delete the webhook subscription for your customer account.

Example Request​

curl -X DELETE "https://api.axro.com/api/v1/webhook/subscription/" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response​

HTTP 204 No Content - The subscription was successfully deleted (no response body).


Webhook Delivery Format​

When events occur, AXRO sends an HTTP POST request to your configured endpoint.

HTTP Request Details​

  • Method: POST
  • Content-Type: application/json
  • Timeout: 10 seconds
  • Retries: Up to three, after roughly 1, 5 and 15 minutes, and only for temporary failures. See Delivery Semantics above for what counts as temporary.
  • Success: Any 2xx HTTP status code
  • Maximum age: One hour. Events waiting longer than that are dropped, see Delivery Semantics above.

HTTP Headers​

Each webhook request includes the following headers:

HeaderDescription
Content-TypeAlways application/json
X-Signature-AlgorithmAlways HMAC-SHA256
X-Signature-TimestampUnix timestamp (seconds) when the signature was generated
X-SignatureHMAC signature in format sha256={hex}
X-Event-IDUnique event identifier (UUID) - use for idempotency
X-Event-TypeEvent type (e.g., customer:address:completed, order:status:changed)
X-Customer-NumberYour customer number

Example headers:

Content-Type: application/json
X-Signature-Algorithm: HMAC-SHA256
X-Signature-Timestamp: 1730798400
X-Signature: sha256=3f6b2c1a4e8d9f0e7c5b3a2d1f9e8c7b6a5d4e3f2c1b0a9f8e7d6c5b4a3d2e1f0
X-Event-ID: 8c1b7c3e-9c5a-4b6a-8a8e-22a6be2a8f41
X-Event-Type: order:created
X-Customer-Number: 123456

Request Body (Event Envelope)​

All webhook payloads follow this canonical envelope structure:

Customer Address Verified Event Example​

{
"id": "8c1b7c3e-9c5a-4b6a-8a8e-22a6be2a8f41",
"type": "customer:address:completed",
"timestamp": "2025-06-25T14:30:00Z",
"customer_number": "123456",
"data": {
"address_id": "03e6ad6b-205d-56fa-9a99-3c6acd0f9123",
"customer_number": "123456"
}
}

Order Status Changed Event Example​

{
"id": "8c1b7c3e-9c5a-4b6a-8a8e-22a6be2a8f41",
"type": "order:status:changed",
"timestamp": "2025-06-25T14:30:00Z",
"customer_number": "123456",
"data": {
"customer_number": "123456",
"order_number": "233945",
"shipment_number": "9876543",
"commission": "my custom reference"
}
}

Order Tracking Created Event Example​

{
"id": "8c1b7c3e-9c5a-4b6a-8a8e-22a6be2a8f41",
"type": "order:tracking:created",
"timestamp": "2025-06-25T14:30:00Z",
"customer_number": "123456",
"data": {
"customer_number": "123456",
"shipment_number": "9876543",
"tracking_code": "1Z657290bb5140c1234",
"carrier": "UPS",
"commission": "my custom reference"
}
}
  • Use shipment_number to look up shipment details via Get Shipment API
  • Use shipment_number to look up tracking info via Get Tracking API
  • Use tracking_code and carrier to provide tracking updates to your customers

Envelope Fields:

  • id: Unique event ID - use for deduplication
  • type: Event type (matches X-Event-Type header)
  • timestamp: ISO 8601 UTC timestamp with Z suffix
  • customer_number: Your customer number
  • data: Event-specific payload (structure varies by event type)

Signature Verification​

To verify the webhook signature and ensure the request is authentic:

  1. Extract the signature components:

    • Get the X-Signature-Timestamp header value
    • Get the X-Signature header value and remove the sha256= prefix
    • Read the raw request body bytes (do not parse or modify)
  2. Construct the signed string:

    string_to_sign = "{timestamp}.{raw_body_bytes}"

    Example: "1730798400.{\"id\":\"8c1b..."}" (timestamp as string, followed by dot, followed by the exact raw body)

  3. Compute the expected signature:

    expected_signature = hex(hmac_sha256(api_token, string_to_sign))
  4. Compare signatures:

    • Use constant-time comparison to prevent timing attacks
    • The computed signature should match the value from the X-Signature header (after removing sha256=)
  5. Validate timestamp (recommended):

    • Check that the timestamp is recent (e.g., within 5 minutes)
    • This prevents replay attacks

Example verification code:

function verifyWebhookSignature($apiToken, $rawBody, $timestamp, $signature) {
// Remove 'sha256=' prefix from signature
$receivedSig = str_replace('sha256=', '', $signature);

// Construct the string to sign
$stringToSign = $timestamp . '.' . $rawBody;

// Compute expected signature
$expectedSig = hash_hmac('sha256', $stringToSign, $apiToken);

// Constant-time comparison
if (!hash_equals($expectedSig, $receivedSig)) {
return false;
}

// Verify timestamp is recent (within 5 minutes)
$now = time();
if (abs($now - intval($timestamp)) > 300) {
return false;
}

return true;
}

// Usage in your webhook endpoint
$apiToken = 'your_secure_webhook_token_here';
$rawBody = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_SIGNATURE_TIMESTAMP'];
$signature = $_SERVER['HTTP_X_SIGNATURE'];

if (!verifyWebhookSignature($apiToken, $rawBody, $timestamp, $signature)) {
http_response_code(401);
exit('Invalid signature');
}

// Process the webhook...
$payload = json_decode($rawBody, true);

Best Practices​

  1. Respond quickly: Return a 2xx status code as soon as you receive and verify the webhook. Process the event asynchronously if needed.
  2. Idempotency: Use the X-Event-ID header to track processed events and prevent duplicate processing.
  3. Validate signatures: Always verify the HMAC signature before processing the payload.
  4. Check timestamps: Reject webhooks with timestamps older than 5 minutes to prevent replay attacks.
  5. Log deliveries: Keep logs of received webhooks for debugging and auditing.
  6. Handle failures gracefully: A temporary outage is retried three times over about 15 minutes, so a short restart is covered. Anything longer is not, and a 4xx answer stops delivery immediately, so make sure your endpoint only rejects requests it genuinely cannot accept.
  7. Use HTTPS: Always use HTTPS endpoints in production to protect the webhook data in transit.