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.
GDPR Webhooks
Every app installed on a LaunchMyStore merchant must respond to three
mandatory GDPR webhook topics. These webhooks are dispatched by the
platform when a merchant initiates a GDPR request through their admin
(or when the merchant closes their store), and your app is legally
required to acknowledge receipt and process the data within fixed
deadlines.
| Topic | Article | Trigger | Deadline (acknowledge / complete) |
|---|
customers/data_request | GDPR Art. 15 (right of access) | Merchant initiates data export for a customer. | 30 days / 90 days |
customers/redact | GDPR Art. 17 (right to erasure) | Merchant initiates data deletion for a customer. | 30 days / 90 days |
shop/redact | GDPR Art. 17 (store closure) | Merchant closes their store. | 30 days / 90 days |
Failure to acknowledge within 30 days marks the request as failed for
your app and notifies the merchant. Failure to complete within 90 days
also marks the request as failed and is grounds for app removal from
the marketplace.
Webhook Delivery
GDPR webhooks are POSTed to two sets of endpoints:
- Any callback URLs registered via the standard webhook subscription
API (
AppWebhook rows with topic matching one of the three GDPR
topics and isEnabled = true).
- The mandatory
gdprUrls configured on your App record:
gdprUrls.customerDataRequest for customers/data_request
gdprUrls.customerRedact for customers/redact
gdprUrls.shopRedact for shop/redact
Webhooks are HMAC-signed with the app’s clientSecret and have a 10s
timeout. Failures are logged but do not automatically retry — your
app must acknowledge receipt via POST /apps/gdpr/acknowledge/:requestId
within the deadline, regardless of HTTP delivery success.
| Header | Value |
|---|
Content-Type | application/json |
X-LMS-Topic | One of customers/data_request, customers/redact, shop/redact |
X-LMS-Gdpr-Request-Id | The requestId (UUID) needed for the acknowledge/complete callbacks |
X-LMS-Hmac-SHA256 | base64(HMAC-SHA256(clientSecret, body)) — verify this matches before processing |
Payload: customers/data_request
{
"shop_id": "f73049dc-b4d4-4f85-99c2-681a5e351a8a",
"shop_domain": "acme-supply.launchmystore.io",
"customer": {
"id": "ac1f2d3e-4b5c-6789-0123-456789abcdef",
"email": "jane@example.com",
"phone": "+15551234567"
},
"orders_requested": true,
"data_request": {
"id": "9f8e7d6c-5b4a-3210-1234-56789abcdef0"
}
}
| Field | Type | Notes |
|---|
shop_id | string (UUID) | Merchant’s storeId. |
shop_domain | string | null | The merchant’s primary storeURL. |
customer.id | string | null | Customer record ID. Null for unidentified customer lookups by email/phone only. |
customer.email | string | null | Customer email (always present unless redacted). |
customer.phone | string | null | Customer phone if provided. |
orders_requested | boolean | true means the merchant wants order history included in the export. |
data_request.id | string (UUID) | The requestId. Use this for acknowledge/complete callbacks. |
Payload: customers/redact
{
"shop_id": "f73049dc-b4d4-4f85-99c2-681a5e351a8a",
"shop_domain": "acme-supply.launchmystore.io",
"customer": {
"id": "ac1f2d3e-4b5c-6789-0123-456789abcdef",
"email": "jane@example.com"
},
"orders_to_redact": [
"ord_1a2b3c4d5e6f7g8h",
"ord_9i8j7k6l5m4n3o2p"
]
}
| Field | Type | Notes |
|---|
shop_id | string (UUID) | Merchant’s storeId. |
shop_domain | string | null | Primary storefront domain. |
customer.id | string | Customer record ID. |
customer.email | string | Customer email at the time of redaction. |
orders_to_redact | string[] | List of all order IDs belonging to this customer. Your app should redact PII from any records tied to these orders, not just the customer record. |
Payload: shop/redact
{
"shop_id": "f73049dc-b4d4-4f85-99c2-681a5e351a8a",
"shop_domain": "acme-supply.launchmystore.io"
}
Fired when a merchant closes their store. Your app must delete all
data associated with shop_id within 90 days.
Required Response
Webhook handlers should return HTTP 200-299 quickly (the platform
timeout is 10s). The HTTP response itself does not acknowledge the
request — you must call the merchant-facing acknowledge endpoint
explicitly:
curl -X POST "https://api.launchmystore.io/apps/gdpr/acknowledge/{requestId}" \
-H "Authorization: Bearer YOUR_APP_ACCESS_TOKEN"
Then, once you have processed the request, mark it complete:
curl -X POST "https://api.launchmystore.io/apps/gdpr/complete/{requestId}" \
-H "Authorization: Bearer YOUR_APP_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"dataExportUrl": "https://your-app.com/exports/abc.zip"
}'
dataExportUrl is only relevant for customers/data_request — it is
displayed in the merchant admin so they can hand the export to the
customer.
Merchant Endpoints
These are used by the LaunchMyStore admin (not your app), but are
documented here for completeness.
POST /apps/gdpr/data-request
Merchant-initiated. Creates a new customers/data_request and dispatches
webhooks to every installed app.
{
"customerId": "ac1f2d3e-...",
"customerEmail": "jane@example.com",
"customerPhone": "+15551234567"
}
Response data:
{
"requestId": "9f8e7d6c-...",
"status": "pending",
"acknowledgeDeadline": "2026-06-15T00:00:00.000Z",
"completionDeadline": "2026-08-14T00:00:00.000Z",
"appsNotified": 7
}
POST /apps/gdpr/customer-redact
{
"customerId": "ac1f2d3e-...",
"customerEmail": "jane@example.com"
}
Response includes ordersToRedact count.
POST /apps/gdpr/shop-redact
No body. Returns the same shape as the others.
GET /apps/gdpr/requests
Lists GDPR requests for the merchant’s store.
| Query | Type | Notes |
|---|
page | integer | Default 1 |
limit | integer | Default 20 |
status | enum | pending, dispatched, acknowledged, completed, failed |
requestType | enum | data_request, customer_redact, shop_redact |
GET /apps/gdpr/requests/:requestId
Returns the full request including per-app acknowledgment state:
{
"requestId": "9f8e7d6c-...",
"requestType": "data_request",
"status": "acknowledged",
"customerId": "ac1f2d3e-...",
"customerEmail": "jane@example.com",
"requestedAt": "2026-05-16T12:34:56.000Z",
"acknowledgeDeadline": "2026-06-15T12:34:56.000Z",
"completionDeadline": "2026-08-14T12:34:56.000Z",
"completedAt": null,
"appAcknowledgments": [
{
"appId": "lms_app_foundry_reviews",
"appName": "Foundry Reviews",
"status": "completed",
"acknowledgedAt": "2026-05-16T12:35:11.000Z",
"completedAt": "2026-05-18T09:00:00.000Z"
},
{
"appId": "lms_app_loyalty_pro",
"appName": "Loyalty Pro",
"status": "pending",
"errorMessage": null
}
],
"dataExportUrl": "https://foundry.example/exports/abc.zip",
"metadata": { "ordersRequested": true }
}
Deadline Enforcement
A cron runs daily at midnight (@Cron(EVERY_DAY_AT_MIDNIGHT)) that:
- Finds requests past
acknowledgeDeadline still in pending /
dispatched, marks the lagging apps as failed, sets the request
status to failed, and re-dispatches the webhook as a final notice.
- Finds requests past
completionDeadline still in dispatched /
acknowledged, marks lagging apps as failed, request → failed.
- Finds requests with
completionDeadline within the next 7 days and
re-dispatches the webhook as a reminder.
HMAC Verification
import crypto from 'crypto';
function verifyGdprWebhook(req, clientSecret) {
const hmacHeader = req.get('X-LMS-Hmac-SHA256');
const body = req.rawBody; // raw bytes, NOT JSON.stringify(req.body)
const expected = crypto
.createHmac('sha256', clientSecret)
.update(body)
.digest('base64');
// constant-time compare
return (
hmacHeader.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(hmacHeader), Buffer.from(expected))
);
}
Status Lifecycle
pending ─► dispatched ─► acknowledged ─► completed
│ │
└──► failed ◄─┘ (deadline missed)
Per-app appAcknowledgments[].status is one of pending,
acknowledged, completed, failed.