LogoPhotopurr Docs
LogoPhotopurr Docs
HomepageWhat is PhotopurrGenerating imagesCreditsAPI reference
Developer Center
Quick startKey managementScopesImage downloadError formatRate limitsImage pack pullingPublish notifications
Developer Center

Publish notifications

Get called when a pack is published, with signature verification and retry rules

If you would rather not poll the image pack pulling endpoints, set an endpoint URL: when the shop owner clicks "Publish" on the website and a new version is actually created, Paitumao POSTs a message to that URL. The message carries only the basics of that version, no images and no text. On receipt, fetch the content with a key that has documents:read via GET /api/v1/image-pack-publications/<id>.

Settings live under "Developer Center → Notifications": enter the URL (https:// only), turn the switch on, copy the signing secret. "Send test notification" sends a ping right away so you can confirm the endpoint is reachable and the signature verifies.

When we send

SituationSent?
"Publish" clicked, version number goes upYes
Repeated submit with the same Idempotency-Key, no new versionNo
Publish rejected (unsaved draft changes, missing images, empty pack)No
Switch off, or URL emptyNo

The request

POST <your URL>
Content-Type: application/json
User-Agent: paitumao-webhook/1
X-Paitumao-Event: image_pack.published
X-Paitumao-Delivery: 7f5b0c9e-…
X-Paitumao-Timestamp: 1757600000
X-Paitumao-Signature: v1=3f1a…
{
  "event": "image_pack.published",
  "publication": {
    "id": 42,
    "pack_id": "pk_xxxxxxxx",
    "version": 3,
    "style_code": "QS-2601",
    "published_at": 1757600000000
  }
}
HeaderMeaning
X-Paitumao-Eventimage_pack.published, or ping from the test button
X-Paitumao-DeliveryId of this delivery. Unchanged across retries; dedupe on it
X-Paitumao-TimestampSend time, Unix seconds. Refreshed on each retry
X-Paitumao-Signaturev1= + hex HMAC-SHA256, see below

The ping body is just {"event":"ping"}, with no publication.

Fields mean the same as in the pulling endpoints: id is the record id you fetch content with, pack_id identifies the product, style_code is display only.

Verifying the signature

The signing secret is shown under "Developer Center → Notifications" and starts with whsec_. Algorithm:

  1. Take ts from the X-Paitumao-Timestamp header and the raw request body body (do not parse and re-serialise; the bytes must match exactly)
  2. Compute HMAC-SHA256(secret, ts + "." + body) as hex
  3. Compare it with the value after v1= in X-Paitumao-Signature using a constant-time comparison
  4. Reject if ts differs from the current time by more than 5 minutes, to block replays

Node.js reference:

import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(secret, headers, rawBody) {
  const ts = headers['x-paitumao-timestamp'];
  const sig = headers['x-paitumao-signature'] ?? '';
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = Buffer.from(
    createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex'),
    'hex'
  );
  for (const part of sig.split(',')) {
    const [v, hex] = part.trim().split('=');
    if (v !== 'v1' || !hex) continue;
    const given = Buffer.from(hex, 'hex');
    if (given.length === expected.length && timingSafeEqual(given, expected)) {
      return true;
    }
  }
  return false;
}

If verification fails, respond 401 and discard. If the secret leaks, click "Regenerate" on the page: the old secret stops working immediately and every later notification is signed with the new one.

Timeouts and retries

  • Each request times out after 8 seconds. Your endpoint only needs to verify, record the id and respond 2xx. Fetch the content afterwards, not before responding.
  • Non-2xx, connection failures and timeouts all count as failures. We then retry twice (after 2 s and 10 s) and give up after the third attempt.
  • Nothing is re-sent after that. Your system should already follow the incremental pulling contract as the fallback: a notification only lets you pull sooner, it is not the only source.
  • Retries keep the same X-Paitumao-Delivery; X-Paitumao-Timestamp and the signature are recomputed. The same notification may arrive twice, so dedupe on the delivery id or on (pack_id, version).
  • Delivery happens in the background after the publish. A failed delivery never affects the publish itself; the outcome is shown under "Last delivery" on the settings page.

URL requirements

  • https:// only. Redirects are not followed (3xx counts as a failure).
  • No localhost, private hostnames or IP literals: Paitumao's servers cannot reach them. For local debugging use a publicly reachable tunnel (for example cloudflared tunnel --url).
  • No username or password in the URL.

Image pack pulling

Incremental pulling of published image packs

Table of Contents

When we sendThe requestVerifying the signatureTimeouts and retriesURL requirements