Webhook Delivery Logs
curl --request GET \
--url https://api.launchmystore.io/apps/store/webhook-logs \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.launchmystore.io/apps/store/webhook-logs"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.launchmystore.io/apps/store/webhook-logs', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.launchmystore.io/apps/store/webhook-logs",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.launchmystore.io/apps/store/webhook-logs"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.launchmystore.io/apps/store/webhook-logs")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.launchmystore.io/apps/store/webhook-logs")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_bodyWebhooks
Webhook Delivery Logs
Inspect, retry, and audit individual webhook delivery attempts
GET
/
apps
/
store
/
webhook-logs
Webhook Delivery Logs
curl --request GET \
--url https://api.launchmystore.io/apps/store/webhook-logs \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.launchmystore.io/apps/store/webhook-logs"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.launchmystore.io/apps/store/webhook-logs', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.launchmystore.io/apps/store/webhook-logs",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.launchmystore.io/apps/store/webhook-logs"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.launchmystore.io/apps/store/webhook-logs")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.launchmystore.io/apps/store/webhook-logs")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_bodyWebhook Delivery Logs
Every webhook dispatch — merchant-registered webhooks and app-scoped webhooks — is persisted as a delivery log. The platform exposes three views over these logs:- Merchant-scoped — store owner/staff inspects deliveries across all apps installed on their store.
- Developer-scoped (per-app) — app owner inspects every delivery to their app, across every install.
- Manual retry — kick off a fresh delivery attempt that respects
the original
secretand exponential-backoff retry queue.
Delivery log fields
| Field | Type | Notes |
|---|---|---|
deliveryId | UUID | Unique id. Surfaced as :deliveryId in URLs. |
webhookId | string | The app or merchant webhook this delivery belongs to. |
webhookType | 'app' | 'merchant' | Whether the webhook is app-registered or merchant-registered. |
storeId | UUID | The merchant who owns this delivery. |
topic | string | e.g. orders/create, customers/redact. |
callbackUrl | string | URL we POST to. |
payload | object | Exact body sent (without HMAC; HMAC is in headers). |
status | enum | PENDING, RETRYING, SUCCESS, FAILED. |
attempts | integer | Number of delivery attempts so far (0-3). |
lastAttemptAt | timestamp | When the most recent send happened. |
nextRetryAt | timestamp | null | When the next retry is scheduled. null once SUCCESS or terminal FAILED. |
responseCode | integer | null | HTTP status from the last attempt. |
responseBody | text | null | First few KB of the response body (truncated). |
errorMessage | text | null | Network error message (timeout, DNS, TLS) if no HTTP response. |
Retry Schedule
Failed dispatches retry 3 times with exponential backoff:| Attempt | Delay after previous | Cumulative time |
|---|---|---|
| 1 (initial) | – | 0 |
| 2 | 1 minute | 1m |
| 3 | 5 minutes | 6m |
| 4 (final) | 15 minutes | 21m |
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"
integer
default:"1"
Page number.
integer
default:"20"
Items per page.
string
Filter:
PENDING, RETRYING, SUCCESS, FAILED.string
Filter by webhook topic (e.g.
orders/create).{
"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 — the platform verifies it first and returns403 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
| HTTP | Error | When |
|---|---|---|
401 | (auth guard) | Missing/invalid merchant JWT. |
403 | Forbidden | Developer endpoints — caller doesn’t own :appId. |
404 | Delivery log not found | :deliveryId doesn’t exist or belongs to another store/app. |
500 | (generic) | DB read error. |
Operational Notes
- The signing secret is captured with each delivery so that retries
(both automatic and manual) can re-sign the body. Rotating
clientSecretdoes not invalidate pending retries — they continue with the secret as of first dispatch. responseBodyis truncated to the first ~64KB. Use it for debugging; do not rely on it for full audit retention.