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.
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.
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 envAPI_ACCESS_KEY_HEADER.Authorization: Bearer <key>— the token must start withssag_.
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 Created — message, gallery, payment_url (checkout link).
JSON body:
title— required string, max 255price— required number, minimum 5description— optional stringis_collection— optional boolean
GET https://dev.snapdevelopment-online.xyz/api/external/v1/galleries/{hash}
200 OK — gallery (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 Created — message, upload (record incl. id), multipart (S3 part size / upload id fields for follow-up calls).
JSON body:
original_filename— required, max 512mime_type— required, max 255file_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 OK — part_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 OK — message, 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 yourwebhook_signing_secret. - User-Agent:
SnapSell-Webhooks/1.0 - Outbound timeout: 25s per attempt.
Header names and signature prefix come from config/api_access.php → webhook (values above are defaults).
Verify signatures
- Read the raw body string before JSON parsing.
t= timestamp header;sig= hex afterv1=on the signature header.- HMAC-SHA256 over
t + "." + rawBodywith your secret; compare lowercase hex tosigwith a constant-time comparison. - 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: