Skip to main content

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.
TopicArticleTriggerDeadline (acknowledge / complete)
customers/data_requestGDPR Art. 15 (right of access)Merchant initiates data export for a customer.30 days / 90 days
customers/redactGDPR Art. 17 (right to erasure)Merchant initiates data deletion for a customer.30 days / 90 days
shop/redactGDPR 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:
  1. Any callback URLs registered via the standard webhook subscription API (AppWebhook rows with topic matching one of the three GDPR topics and isEnabled = true).
  2. 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.

Request Headers

HeaderValue
Content-Typeapplication/json
X-LMS-TopicOne of customers/data_request, customers/redact, shop/redact
X-LMS-Gdpr-Request-IdThe requestId (UUID) needed for the acknowledge/complete callbacks
X-LMS-Hmac-SHA256base64(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"
  }
}
FieldTypeNotes
shop_idstring (UUID)Merchant’s storeId.
shop_domainstring | nullThe merchant’s primary storeURL.
customer.idstring | nullCustomer record ID. Null for unidentified customer lookups by email/phone only.
customer.emailstring | nullCustomer email (always present unless redacted).
customer.phonestring | nullCustomer phone if provided.
orders_requestedbooleantrue means the merchant wants order history included in the export.
data_request.idstring (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"
  ]
}
FieldTypeNotes
shop_idstring (UUID)Merchant’s storeId.
shop_domainstring | nullPrimary storefront domain.
customer.idstringCustomer record ID.
customer.emailstringCustomer email at the time of redaction.
orders_to_redactstring[]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.
QueryTypeNotes
pageintegerDefault 1
limitintegerDefault 20
statusenumpending, dispatched, acknowledged, completed, failed
requestTypeenumdata_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:
  1. 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.
  2. Finds requests past completionDeadline still in dispatched / acknowledged, marks lagging apps as failed, request → failed.
  3. 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.