Developer dashboard

Advanced Webhooks#

Advanced webhook configuration: retries, signatures, event filtering and delivery history.

All endpoints below are admin API routes: authenticate with Authorization: Bearer <JWT> (or an API key in x-api-key). A merchant-scoped caller can only see and manage its own merchant's endpoints.


Webhook Retry Configuration#

Retries are configured per endpoint with two fields, set on create or update:

Field Type Default Description
maxRetries integer 3 Total delivery attempts, including the first one
retryDelaySeconds integer 60 Base delay; doubles after each failed attempt

Endpoint#

PATCH/api/v1/webhooks/endpoints/:id
Webhook Retry Configurationbash
curl -X PATCH https://uat.auxcore.net/api/v1/webhooks/endpoints/7c9e6679-7425-40de-944b-e07fc1f90ae7 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "maxRetries": 5,
    "retryDelaySeconds": 120
  }'

Other updatable fields: url, description, events, isActive.

How retries work#

  • A delivery succeeds only on an HTTP 2xx response within 30 seconds. Anything else (non-2xx, timeout, connection error) is a failure.
  • After failed attempt n, if n < maxRetries, the delivery is set to retrying with the next attempt at retryDelaySeconds × 2^(n-1) (defaults: 60 s, then 120 s).
  • A background sweep picks up due retries about once a minute, up to 100 per sweep, so actual retry times can lag by up to a minute.
  • When attempts are exhausted the delivery stays failed. A delivery for an endpoint that has since been deactivated or deleted is marked failed without being sent.

Webhook Signatures#

Every delivery is signed with the endpoint's secret (HMAC-SHA256).

Headers sent#

Header Value
X-Webhook-Signature t=<unix seconds>,v1=<hex HMAC-SHA256>
X-Webhook-Event-Type Event name, e.g. transaction.approved
X-Webhook-Event-ID Event id (same for every retry of the event)
X-Webhook-Delivery-ID Delivery id
User-Agent AuxVault-Webhooks/1.0
Content-Type application/json

Verifying#

The signed string is <t>.<raw request body>. Compute HMAC-SHA256 over it with your secret and compare (constant-time) to v1. Reject timestamps more than 5 minutes from your clock to prevent replays. Always verify against the raw body bytes, not a re-serialised parsed object.

javascript
const crypto = require('crypto');

function verify(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
  const t = Number(parts.t);
  if (!t || !parts.v1 || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`, 'utf8').digest('hex');
  return crypto.timingSafeEqual(Buffer.from(parts.v1, 'hex'), Buffer.from(expected, 'hex'));
}

Payload envelope#

json
{
  "id": "0b7f5c2e-3d1a-4f6b-9e8c-1a2b3c4d5e6f",
  "type": "transaction.approved",
  "created": "2026-01-28T15:00:00.000Z",
  "data": { "...": "event-specific fields" },
  "metadata": {
    "tenantId": "your-tenant-id",
    "merchantId": "3f2a9c1e-7b4d-4e8a-9f10-2c5d6e7f8a90"
  }
}

Secret handling#

The signing secret is returned only in the create response and by regenerate-secret. List/get/update responses mask it (hasSecret, secretHint with the last 4 characters).

POST/api/v1/webhooks/endpoints/:id/regenerate-secret

Response: { "success": true, "data": { "secret": "…" } } — the old secret stops working immediately.


Webhook Filtering#

Subscribe to specific events by listing them in events. Names must match exactly — wildcards (transaction.*) and payload filters (e.g. amount thresholds) are not supported.

Endpoint#

POST/api/v1/webhooks/endpoints
Webhook Filteringbash
curl -X POST https://uat.auxcore.net/api/v1/webhooks/endpoints \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/webhook",
    "description": "Order system",
    "events": ["transaction.approved", "transaction.declined", "transaction.refunded"],
    "maxRetries": 3,
    "retryDelaySeconds": 60
  }'
Field Required Description
url Yes Must be a valid URL
events Yes Array of event names (below)
description No Free text
merchantId No Only for tenant-level callers; a merchant-scoped caller always gets its own merchant
maxRetries No Default 3
retryDelaySeconds No Default 60

Response (201): the endpoint plus secret, and "message": "Store the signing secret now — it cannot be retrieved again."

Event types#

Event When
transaction.approved Payment approved
transaction.pending Payment accepted but not yet settled (e.g. ACH)
transaction.declined Payment declined
transaction.voided Transaction voided
transaction.refunded Refund processed
transaction.refund_failed Refund failed
transaction.returned ACH debit returned by the bank (data.returnCode carries the R-code)
session.cancelled Hosted checkout session cancelled
session.expired Hosted checkout session expired

batch.resubmit is a platform event delivered only to system endpoints and cannot be subscribed to from a merchant endpoint. Unknown names are rejected with 400 UNKNOWN_EVENT_TYPE (the message lists every invalid name and the valid set). See Webhook Events for payloads.

Errors#

Status Code When
400 VALIDATION_ERROR url missing, or events missing or not an array
400 UNKNOWN_EVENT_TYPE An event name is not in the list above
400 INVALID_URL url is not a valid URL
404 ENDPOINT_NOT_FOUND Endpoint not found (or not yours)

Testing an Endpoint#

POST/api/v1/webhooks/endpoints/:id/test

Sends a signed webhook.test event to the endpoint and returns the delivery result:

Testing an Endpointjson
{
  "success": true,
  "data": {
    "success": true,
    "deliveryId": "5f0c1d2e-8a9b-4c3d-9e1f-2a3b4c5d6e7f",
    "statusCode": 200,
    "responseTime": 184
  }
}

A failed delivery still returns 200 with data.success: false and an error message.


Webhook Logs#

View delivery history for an endpoint, newest first.

GET/api/v1/webhooks/endpoints/:id/deliveries
Query Default Description
limit 50 Page size
offset 0 Rows to skip
Webhook Logsbash
curl "https://uat.auxcore.net/api/v1/webhooks/endpoints/7c9e6679-7425-40de-944b-e07fc1f90ae7/deliveries?limit=20" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

Webhook Logsjson
{
  "success": true,
  "data": {
    "deliveries": [
      {
        "id": "5f0c1d2e-8a9b-4c3d-9e1f-2a3b4c5d6e7f",
        "eventType": "transaction.approved",
        "eventId": "0b7f5c2e-3d1a-4f6b-9e8c-1a2b3c4d5e6f",
        "attemptNumber": 2,
        "status": "retrying",
        "responseStatusCode": 503,
        "errorMessage": "HTTP 503: Service Unavailable",
        "responseTimeMs": 412,
        "nextRetryAt": "2026-01-28T15:03:00.000Z",
        "createdAt": "2026-01-28T15:00:00.000Z"
      }
    ],
    "pagination": { "total": 1, "limit": 20, "offset": 0, "hasMore": false }
  }
}

Delivery status is one of pending, success, failed, retrying.

Retry a delivery manually#

POST/api/v1/webhooks/deliveries/:deliveryId/retry

Re-sends the delivery immediately (incrementing attemptNumber) and returns the same result shape as the test endpoint. The retry does not check whether the endpoint is still active, and it is counted as an attempt (if it fails and the attempts are already used up, no further automatic retry is scheduled). An unknown delivery, or one belonging to another tenant or merchant, returns 404 NOT_FOUND.


Need help? Contact support@auxcore.net