Developer reference

External API & sale webhooks

REST under /api/external/v1 with your access key. When a gallery purchase fulfills, we POST a signed webhook to your endpoint.

7 routes HMAC SHA-256 100 req/min
dev.snapdevelopment-online.xyz · external/v1
POST /galleries create
GET /galleries/{hash}
POST /galleries/{hash}/items
POST /uploads/direct/init
POST /uploads/direct/{id}/part-url
POST /uploads/direct/{id}/complete
DEL /uploads/direct/{id}

→ webhook
POST your-url gallery.sale.completed

API prefix: https://dev.snapdevelopment-online.xyz/api/external/v1/

01 Overview

This page documents only:

  • The seven REST routes mounted at https://dev.snapdevelopment-online.xyz/api/external/v1 (galleries and direct multipart uploads).
  • Outbound webhooks SnapSell sends to your HTTPS URL after a gallery purchase is fulfilled (queued delivery, signed payload).

Issuing the API key, setting your webhook URL, and rotating keys are done inside the SnapSell product. You will receive a plaintext key once; store it securely. To request programmatic access from the app, the account must already be KYC verified and have a verified phone number; the access-status endpoint exposes kyc_verified and phone_verified for the client UI.

Requirements. The seller account tied to the key must have programmatic access approved by an administrator, a configured wallet / currency for gallery and upload calls, and remain an eligible agency account. Allowed file types for direct upload follow the server allow-list (filename + MIME).

02 Authentication & rate limits

API access key

Send the full plaintext key on every request, using either:

  • X-Api-Access-Key: <key> — override the header name with env API_ACCESS_KEY_HEADER.
  • Authorization: Bearer <key> — the token must start with ssag_.

Format: ssag_live_{secret}

Rate limiting

100 requests per minute per seller account (owner of the key), not per IP. 429 Too Many Requests includes a JSON message and retry_after when applicable. Standard X-RateLimit-* headers apply where configured.

03 REST endpoints

Path parameter {hash} is the gallery’s public hash. {upload} is the integer upload id from POST …/uploads/direct/init.

POST https://dev.snapdevelopment-online.xyz/api/external/v1/galleries

Create a gallery. 201 Createdmessage, gallery, payment_url (checkout link).

JSON body:

  • title — required string, max 255
  • price — required number, minimum 5
  • description — optional string
  • is_collection — optional boolean

GET https://dev.snapdevelopment-online.xyz/api/external/v1/galleries/{hash}

200 OKgallery (with uploads), payment_url.

POST https://dev.snapdevelopment-online.xyz/api/external/v1/galleries/{hash}/items

Attach existing uploads you own. 200 OK — message, updated gallery, payment_url.

JSON body: upload_ids — required array of 1–100 integers (existing uploads.id values).

POST https://dev.snapdevelopment-online.xyz/api/external/v1/uploads/direct/init

Start a multipart upload to object storage. 201 Createdmessage, upload (record incl. id), multipart (S3 part size / upload id fields for follow-up calls).

JSON body:

  • original_filename — required, max 512
  • mime_type — required, max 255
  • file_size — required integer bytes, 1 … 5,368,709,120 (default max)

422 if file type not allowed. 503 on storage or configuration errors.

POST https://dev.snapdevelopment-online.xyz/api/external/v1/uploads/direct/{upload}/part-url

Get a presigned PUT URL for one part. 200 OKpart_number, url, method (PUT), headers.

JSON body: part_number — required integer ≥ 1 (max 10000).

Upload raw bytes to the URL; collect each part’s ETag for complete.

POST https://dev.snapdevelopment-online.xyz/api/external/v1/uploads/direct/{upload}/complete

Finalize the multipart upload. 200 OKmessage, upload.

JSON body: parts — required non-empty array of { "part_number": int, "etag": string } (ETag values from S3).

DELETE https://dev.snapdevelopment-online.xyz/api/external/v1/uploads/direct/{upload}

Abort an in-progress direct upload. 200 OK — confirmation message. 409 if nothing to abort.

04 Webhooks after sale

When it fires

After a gallery order is fulfilled (payment completed and order processed), SnapSell enqueues an outbound HTTP request to the webhook URL configured for your active API access key. If no URL is set or no signing secret exists, no webhook is sent.

Delivery runs on the queue worker; failed deliveries are retried according to your queue configuration.

HTTP request

  • Method: POST
  • Body: JSON (UTF-8). The exact raw body bytes are included in the signature.
  • Content-Type: application/json
  • X-SnapSell-Webhook-Timestamp: Unix timestamp in seconds (string).
  • X-SnapSell-Webhook-Signature: v1= + lowercase hex HMAC-SHA256 of {timestamp}.{raw_body} using your webhook_signing_secret.
  • User-Agent: SnapSell-Webhooks/1.0
  • Outbound timeout: 25s per attempt.

Header names and signature prefix come from config/api_access.phpwebhook (values above are defaults).

Verify signatures

  1. Read the raw body string before JSON parsing.
  2. t = timestamp header; sig = hex after v1= on the signature header.
  3. HMAC-SHA256 over t + "." + rawBody with your secret; compare lowercase hex to sig with a constant-time comparison.
  4. Optionally reject timestamps outside a short window (e.g. ±5 minutes) against replay.

Payload

Event type is always gallery.sale.completed. api_version matches server config (currently 2026-04-07).

{
  "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
  "type": "gallery.sale.completed",
  "api_version": "2026-04-07",
  "created_at": "2026-04-07T12:00:00+00:00",
  "data": {
    "transaction_uuid": "...",
    "gallery_id": 1,
    "gallery_hash": "...",
    "gallery_title": "...",
    "amount": "19.99",
    "currency_code": "EUR",
    "seller_earning": "17.50",
    "buyer_email": "buyer@example.com",
    "payment_gateway": "stripe",
    "completed_at": "2026-04-07T12:00:00+00:00"
  }
}

05 Errors

Code Typical cause
401 Missing, malformed, or revoked API key; account no longer valid for key use.
403 Wallet / currency not configured; policy denies the resource.
404 Unknown gallery hash; upload id not owned by the key’s user.
409 Multipart state conflict (e.g. abort when not in direct-upload state).
422 Validation errors; disallowed file type on init; invalid complete payload.
429 Programmatic rate limit exceeded.
503 Storage or upstream failure (see JSON message).

06 cURL examples

Replace ssag_live_YOUR_KEY_HERE, hashes, and ids. Base: https://dev.snapdevelopment-online.xyz/api/external/v1

Create gallery

curl -sS -X POST "https://dev.snapdevelopment-online.xyz/api/external/v1/galleries" \
  -H "Content-Type: application/json" \
  -H "X-Api-Access-Key: ssag_live_YOUR_KEY_HERE" \
  -d '{"title":"Summer pack","price":9.99,"description":"Optional"}'

Get gallery

curl -sS "https://dev.snapdevelopment-online.xyz/api/external/v1/galleries/GALLERY_HASH" \
  -H "X-Api-Access-Key: ssag_live_YOUR_KEY_HERE"

Add uploads to gallery

curl -sS -X POST "https://dev.snapdevelopment-online.xyz/api/external/v1/galleries/GALLERY_HASH/items" \
  -H "Content-Type: application/json" \
  -H "X-Api-Access-Key: ssag_live_YOUR_KEY_HERE" \
  -d '{"upload_ids":[101,102]}'

Direct upload — init

curl -sS -X POST "https://dev.snapdevelopment-online.xyz/api/external/v1/uploads/direct/init" \
  -H "Content-Type: application/json" \
  -H "X-Api-Access-Key: ssag_live_YOUR_KEY_HERE" \
  -d '{"original_filename":"clip.mp4","mime_type":"video/mp4","file_size":1048576}'

Direct upload — part URL

curl -sS -X POST "https://dev.snapdevelopment-online.xyz/api/external/v1/uploads/direct/UPLOAD_ID/part-url" \
  -H "Content-Type: application/json" \
  -H "X-Api-Access-Key: ssag_live_YOUR_KEY_HERE" \
  -d '{"part_number":1}'

Direct upload — complete

curl -sS -X POST "https://dev.snapdevelopment-online.xyz/api/external/v1/uploads/direct/UPLOAD_ID/complete" \
  -H "Content-Type: application/json" \
  -H "X-Api-Access-Key: ssag_live_YOUR_KEY_HERE" \
  -d '{"parts":[{"part_number":1,"etag":"\"abc123def456\""}]}'

Direct upload — abort

curl -sS -X DELETE "https://dev.snapdevelopment-online.xyz/api/external/v1/uploads/direct/UPLOAD_ID" \
  -H "X-Api-Access-Key: ssag_live_YOUR_KEY_HERE"

07 Support

Need a key or webhook URL?

Configuration is done in the SnapSell app and admin workflows. For integration help:

support@snapsell.com

← Back to SnapSell