Skip to main content
GET
/
apps
/
store
/
webhook-logs
Webhook Delivery Logs
curl --request GET \
  --url https://api.launchmystore.io/apps/store/webhook-logs \
  --header 'Authorization: Bearer <token>'

Documentation Index

Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt

Use this file to discover all available pages before exploring further.

Webhook Delivery Logs

Every webhook dispatch — merchant-registered webhooks and app-scoped webhooks — is persisted as a WebhookDeliveryLog row. The platform exposes three views over this table:
  1. Merchant-scoped — store owner/staff inspects deliveries across all apps installed on their store.
  2. Developer-scoped (per-app) — app owner inspects every delivery to their app, across every install.
  3. Manual retry — kick off a fresh delivery attempt that respects the original secret and exponential-backoff retry queue.
All endpoints require merchant JWT auth (UserRole.MERCHANT or UserRole.STAFF_ADMIN). There is no public app-scoped endpoint — apps can subscribe to delivery-failure notifications, but cannot read merchant logs over the OAuth API.

Data Model: WebhookDeliveryLog

FieldTypeNotes
deliveryIdUUIDPrimary key. Surfaced as :deliveryId in URLs.
webhookIdstringReferences AppWebhook.appWebhookId (apps) or Webhook.webhookId (merchant).
webhookType'app' | 'merchant'Which table webhookId points to.
storeIdUUIDThe merchant who owns this delivery.
topicstringe.g. orders/create, customers/redact.
callbackUrlstringURL we POST to.
payloadJSONBExact body sent (without HMAC; HMAC is in headers).
statusenumPENDING, RETRYING, SUCCESS, FAILED.
attemptsintegerNumber of delivery attempts so far (0-3).
lastAttemptAttimestampWhen the most recent send happened.
nextRetryAttimestamp | nullWhen the worker will retry. null once SUCCESS or terminal FAILED.
responseCodeinteger | nullHTTP status from the last attempt.
responseBodytext | nullFirst few KB of the response body (truncated).
errorMessagetext | nullNetwork error message (timeout, DNS, TLS) if no HTTP response.
secretstring | nullHMAC secret used for signing — stored so retries can re-sign.
Indexed on (storeId, status), (status, nextRetryAt) (the retry-queue worker scan), and (webhookId).

Retry Schedule

Failed dispatches retry 3 times with exponential backoff:
AttemptDelay after previousCumulative time
1 (initial)0
21 minute1m
35 minutes6m
4 (final)15 minutes21m
After the fourth failed attempt, status is set to FAILED and nextRetryAt is cleared. The delivery can still be retried manually via the /retry endpoint below — this re-arms the row but starts the attempt counter fresh.

Merchant Endpoints

GET /apps/store/webhook-logs

List webhook deliveries for the caller’s store.
curl -X GET "https://api.launchmystore.io/apps/store/webhook-logs?page=1&limit=20&status=FAILED" \
  -H "Authorization: Bearer MERCHANT_JWT"
page
integer
default:"1"
Page number.
limit
integer
default:"20"
Items per page.
status
string
Filter: PENDING, RETRYING, SUCCESS, FAILED.
topic
string
Filter by webhook topic (e.g. orders/create).
Response:
{
  "status": 200,
  "state": "success",
  "data": {
    "items": [
      {
        "deliveryId": "d3a1...-...-...",
        "webhookId": "w8b2...",
        "webhookType": "app",
        "topic": "orders/create",
        "callbackUrl": "https://your-app.com/webhooks/orders",
        "status": "FAILED",
        "attempts": 4,
        "lastAttemptAt": "2026-05-16T12:55:00.000Z",
        "nextRetryAt": null,
        "responseCode": 503,
        "errorMessage": null
      }
    ],
    "pagination": { "page": 1, "limit": 20, "total": 37, "hasMore": true }
  }
}

GET /apps/store/webhook-logs/:deliveryId

Return the full record including payload and responseBody.
{
  "status": 200,
  "state": "success",
  "data": {
    "deliveryId": "d3a1...",
    "topic": "orders/create",
    "callbackUrl": "https://your-app.com/webhooks/orders",
    "payload": { "id": "ord_1a2b", "total_price": "29.99", "...": "..." },
    "status": "FAILED",
    "attempts": 4,
    "responseCode": 503,
    "responseBody": "Service Unavailable\n",
    "errorMessage": null,
    "secret": "ws_sec_..."
  }
}
404 Delivery log not found when no row matches (deliveryId, storeId).

POST /apps/store/webhook-logs/:deliveryId/retry

Manually trigger a fresh send. The retry uses the original secret, callbackUrl, and payload from the row — only attempts is reset.
{
  "status": 200,
  "state": "success",
  "message": "Webhook delivery retry initiated",
  "data": { "deliveryId": "d3a1...", "status": "PENDING", "attempts": 0 }
}

Developer Endpoints (per-app, all stores)

These views require ownership of the app — verifyAppOwnership(appId, storeId) runs first and returns 403 Forbidden if the JWT’s storeId is not the app’s developerId.

GET /apps/developer/:appId/webhook-deliveries

Same query parameters as the merchant endpoint, but scoped to one app across all of its installations.

GET /apps/developer/:appId/webhook-deliveries/stats

Aggregate counters (last 24h / 7d / 30d) plus per-topic failure rates — useful for the developer dashboard’s health page.
{
  "status": 200,
  "state": "success",
  "data": {
    "last24h": { "total": 4821, "success": 4789, "failed": 32 },
    "last7d":  { "total": 31204, "success": 30988, "failed": 216 },
    "byTopic": [
      { "topic": "orders/create", "total": 1942, "failureRate": 0.4 },
      { "topic": "customers/update", "total": 612, "failureRate": 1.1 }
    ]
  }
}

GET /apps/developer/:appId/webhook-deliveries/:deliveryId

Same as the merchant single-delivery view but scoped to one app.

POST /apps/developer/:appId/webhook-deliveries/:deliveryId/retry

Manually retry. Same semantics as the merchant retry, scoped to one app.

Error Codes

HTTPErrorWhen
401(auth guard)Missing/invalid merchant JWT.
403ForbiddenDeveloper endpoints — caller doesn’t own :appId.
404Delivery log not found:deliveryId doesn’t exist or belongs to another store/app.
500(generic)DB read error.

Operational Notes

  • The HMAC secret column is stored on the log row so that retries (both automatic and manual) can re-sign the body without re-deriving from the app’s clientSecret. Rotating clientSecret does not invalidate pending retries — they continue with the secret as of first dispatch.
  • responseBody is truncated to the first ~64KB. Use it for debugging; do not rely on it for full audit retention.
  • For the live retry-queue scan, see WebhookDispatchServiceidx_webhook_delivery_logs_retry_queue (status, nextRetryAt) keeps the worker’s SELECT ... WHERE status = 'RETRYING' AND nextRetryAt < NOW() cheap even with millions of rows.