SnapSell
Sign up

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
staging.snapselldev.eu · external/v1
GET /galleries
POST /galleries
GET /galleries/{gallery_hash}
POST /galleries/{gallery_hash}/items
DELETE /galleries/{gallery_hash}
POST /uploads/simple/init
POST /uploads/simple/{upload_id}/complete

→ webhook on sale
POST your-url

API prefix: https://staging.snapselldev.eu/api/external/v1/

01 Overview

This page documents only:

  • The seven REST routes mounted at https://staging.snapselldev.eu/api/external/v1 (galleries and file uploads).
  • Outbound webhooks SnapSell sends to your HTTPS URL after a gallery purchase is completed.

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

Requirements. Your account must have API access approved by an administrator and a currency configured in your profile. Only certain file types are accepted for uploads — you'll get a clear error if a file is not supported.

02 Authentication & rate limits

API access key

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

  • Authorization: Bearer YOUR_KEY_HERE

Rate limiting

100 requests per minute per account. If you exceed this you'll get a 429 response with a retry_after value (in seconds) telling you when to try again.

03 REST endpoints

{gallery_hash} is the gallery’s public hash returned when creating a gallery. {upload_id} is the integer id returned by POST /uploads/simple/init. All requests to our API require the Authorization: Bearer header — except the S3 PUT step, which uses only the presigned URL.

GET https://staging.snapselldev.eu/api/external/v1/galleries

Returns all galleries belonging to your account, paginated newest first. Each gallery includes its uploads and a ready-to-share payment_url. Galleries with no uploads are excluded.

Query parameters

  • page — page number (default 1)
  • per_page — results per page; default 15, max 50

Response — 200 OK

{
  "galleries": [
    {
      "id": 42,
      "user_id": 7,
      "title": "Summer pack",
      "description": "Optional",
      "price": "9.99",
      "hash": "SEf4rXKf9y",
      "status": "active",
      "is_deleted": false,
      "is_collection": false,
      "is_adult_content": false,
      "views": 0,
      "created_at": "2026-06-13T11:07:46.000000Z",
      "updated_at": "2026-06-13T11:07:46.000000Z",
      "deleted_at": null,
      "uploads_count": 2,
      "payment_url": "https://staging.snapselldev.eu/buy/SEf4rXKf9y",
      "uploads": [
        {
          "id": 101,
          "product_id": "85de3406-3c8c-42ef-b0a7-d99b3b7fc249",
          "user_id": 7,
          "original_filename": "photo.png",
          "mime_type": "image/png",
          "file_size": 335872,
          "file_path_original": "users/7/products/DbzK5gk8TwAK21gwct2GZF49r8qdIuYC",
          "file_path_preview": null,
          "status": "processed",
          "storage_disk": "s3",
          "local_backup_path": null,
          "uploaded_to_s3_at": "2026-06-13T11:14:36.000000Z",
          "s3_upload_attempts": 0,
          "preview": null,
          "thumbnail": null,
          "created_at": "2026-06-13T11:13:42.000000Z",
          "updated_at": "2026-06-13T11:17:28.000000Z",
          "deleted_at": null,
          "pivot": {
            "gallery_id": 42,
            "upload_id": 101,
            "order": 0,
            "created_at": "2026-06-13T11:17:28.000000Z",
            "updated_at": "2026-06-13T11:17:28.000000Z"
          }
        }
      ]
    }
  ],
  "current_page": 1,
  "last_page": 1,
  "per_page": 15,
  "total": 1
}

POST https://staging.snapselldev.eu/api/external/v1/galleries

Create a new gallery. Returns the created gallery object and a checkout link your buyers can use to purchase.

Request body

  • title — required string, max 255
  • price — required number, minimum 5
  • description — optional string
  • is_collection — optional boolean (default false)
{
  "title": "Summer pack",
  "price": 9.99,
  "description": "Optional"
}

Response — 201 Created

{
  "message": "Gallery created successfully.",
  "gallery": {
    "id": 42,
    "user_id": 7,
    "title": "Summer pack",
    "description": "Optional",
    "price": "9.99",
    "hash": "8pmTfurIG3",
    "status": "active",
    "is_deleted": false,
    "is_collection": false,
    "is_adult_content": false,
    "views": 0,
    "created_at": "2026-06-13T11:22:05.000000Z",
    "updated_at": "2026-06-13T11:22:05.000000Z",
    "deleted_at": null
  },
  "payment_url": "https://staging.snapselldev.eu/buy/8pmTfurIG3"
}

GET https://staging.snapselldev.eu/api/external/v1/galleries/{gallery_hash}

Fetch a single gallery with all its attached uploads.

Response — 200 OK

{
  "gallery": {
    "id": 42,
    "user_id": 7,
    "title": "Summer pack",
    "description": "Optional",
    "price": "9.99",
    "hash": "SEf4rXKf9y",
    "status": "active",
    "is_deleted": false,
    "is_collection": false,
    "is_adult_content": false,
    "views": 0,
    "created_at": "2026-06-13T11:07:46.000000Z",
    "updated_at": "2026-06-13T11:07:46.000000Z",
    "deleted_at": null,
    "uploads": [
      {
        "id": 101,
        "product_id": "85de3406-3c8c-42ef-b0a7-d99b3b7fc249",
        "user_id": 7,
        "original_filename": "photo.png",
        "mime_type": "image/png",
        "file_size": 335872,
        "file_path_original": "users/7/products/DbzK5gk8TwAK21gwct2GZF49r8qdIuYC",
        "file_path_preview": null,
        "status": "processed",
        "storage_disk": "s3",
        "local_backup_path": null,
        "uploaded_to_s3_at": "2026-06-13T11:14:36.000000Z",
        "s3_upload_attempts": 0,
        "preview": null,
        "thumbnail": null,
        "created_at": "2026-06-13T11:13:42.000000Z",
        "updated_at": "2026-06-13T11:17:28.000000Z",
        "deleted_at": null,
        "pivot": {
          "gallery_id": 42,
          "upload_id": 101,
          "order": 0,
          "created_at": "2026-06-13T11:17:28.000000Z",
          "updated_at": "2026-06-13T11:17:28.000000Z"
        }
      }
    ]
  },
  "payment_url": "https://staging.snapselldev.eu/buy/SEf4rXKf9y"
}

POST https://staging.snapselldev.eu/api/external/v1/uploads/simple/init

Start a file upload. Returns a presigned S3 PUT URL valid for up to 24 hours. No multipart setup required — you send the entire file in one PUT. Save the upload.id and the url from this response for the next two steps.

Request body

  • original_filename — required string, max 512
  • mime_type — required string, max 255
  • file_size — required integer (bytes), 1 – 5,368,709,120 (5 GiB max)
{
  "original_filename": "photo.png",
  "mime_type": "image/png",
  "file_size": 335872
}

Response — 201 Created

{
  "message": "PUT the file bytes to the url using the provided headers, then call complete.",
  "upload": {
    "id": 101,
    "product_id": "472e30a4-111f-4274-87ee-f447ae635937",
    "user_id": 7,
    "original_filename": "photo.png",
    "mime_type": "image/png",
    "file_size": 335872,
    "file_path_original": "users/7/products/DLbsWD2IWwZdlF60AyDb8ZPaj55bwi8U",
    "file_path_preview": null,
    "status": "pending_direct_upload",
    "storage_disk": "s3",
    "local_backup_path": null,
    "uploaded_to_s3_at": null,
    "s3_upload_attempts": 0,
    "preview": null,
    "thumbnail": null,
    "created_at": "2026-06-13T11:24:59.000000Z",
    "updated_at": "2026-06-13T11:24:59.000000Z",
    "deleted_at": null
  },
  "url": "https://your-bucket.s3.region.amazonaws.com/users/7/products/DLbsWD2IWwZdlF60AyDb8ZPaj55bwi8U?X-Amz-Algorithm=AWS4-HMAC-SHA256&...",
  "method": "PUT",
  "headers": {
    "Content-Type": "image/png"
  }
}

PUT {url}  — directly to S3, not our API

Send the raw file bytes directly to the presigned URL returned by init. This request goes straight to S3 — do not include your Authorization header here, the URL is already signed.

Important: Set the Content-Type header to the exact mime type you passed to init (e.g. image/png). Upload file as a binary file. The response body will be empty — a 200 OK status means the upload succeeded.

Required header

  • Content-Type: image/png — must match the mime_type from init

Response — 200 OK

Empty body. A 200 status confirms the file reached S3.

POST https://staging.snapselldev.eu/api/external/v1/uploads/simple/{upload_id}/complete

Notify the server that the S3 PUT succeeded. The server verifies the file exists on storage, marks the upload ready, and queues preview generation. The upload’s id is then ready to add to a gallery.

Request body

None required.

Response — 200 OK

{
  "message": "File uploaded to storage. Preview processing has started.",
  "upload": {
    "id": 101,
    "product_id": "85de3406-3c8c-42ef-b0a7-d99b3b7fc249",
    "user_id": 7,
    "original_filename": "photo.png",
    "mime_type": "image/png",
    "file_size": 335872,
    "file_path_original": "users/7/products/DbzK5gk8TwAK21gwct2GZF49r8qdIuYC",
    "file_path_preview": null,
    "status": "pending_processing",
    "storage_disk": "s3",
    "local_backup_path": null,
    "uploaded_to_s3_at": "2026-06-13T11:14:36.000000Z",
    "s3_upload_attempts": 0,
    "preview": null,
    "thumbnail": null,
    "created_at": "2026-06-13T11:13:42.000000Z",
    "updated_at": "2026-06-13T11:14:36.000000Z",
    "deleted_at": null
  }
}

Error — 409 Conflict

Returned if the file is not yet found on storage — ensure the S3 PUT returned 200 before calling complete.

POST https://staging.snapselldev.eu/api/external/v1/galleries/{gallery_hash}/items

Attach one or more uploads to a gallery. Each upload can only belong to one gallery — passing an upload that is already in a different gallery returns a 422 with the conflicting ids listed.

Request body

  • upload_ids — required array of 1–100 integers (upload.id values from complete)
{
  "upload_ids": [101, 102]
}

Response — 200 OK

{
  "message": "2 uploads added to gallery successfully.",
  "gallery": {
    "id": 42,
    "user_id": 7,
    "title": "Summer pack",
    "description": "Optional",
    "price": "9.99",
    "hash": "SEf4rXKf9y",
    "status": "active",
    "is_deleted": false,
    "is_collection": false,
    "is_adult_content": false,
    "views": 0,
    "created_at": "2026-06-13T11:07:46.000000Z",
    "updated_at": "2026-06-13T11:07:46.000000Z",
    "deleted_at": null,
    "uploads": [
      {
        "id": 101,
        "original_filename": "photo.png",
        "mime_type": "image/png",
        "file_size": 335872,
        "status": "processed",
        ...
        "pivot": { "gallery_id": 42, "upload_id": 101, "order": 0, ... }
      },
      {
        "id": 102,
        "original_filename": "video.mp4",
        "mime_type": "video/mp4",
        "file_size": 10485760,
        "status": "processed",
        ...
        "pivot": { "gallery_id": 42, "upload_id": 102, "order": 1, ... }
      }
    ]
  },
  "payment_url": "https://staging.snapselldev.eu/buy/SEf4rXKf9y"
}

Error — 422 (upload already in another gallery)

{
  "message": "One or more uploads are already assigned to another gallery.",
  "conflicting_upload_ids": [102]
}

DELETE https://staging.snapselldev.eu/api/external/v1/galleries/{gallery_hash}

Permanently delete a gallery. The attached uploads are not deleted — they remain in your account and can be added to a new gallery.

Response — 200 OK

{
  "message": "Gallery deleted successfully."
}

04 Webhooks after sale

When it fires

After a buyer successfully completes a purchase, SnapSell sends a POST request to the webhook URL you configured in your dashboard. If no URL is set, nothing is sent. Failed deliveries are retried automatically.

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.

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

The payload below is what your server receives after every completed sale.

{
  "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 Upload operation not allowed in the current upload state (e.g. completing an upload that was already aborted).
422 Validation errors; disallowed file type on init; invalid complete payload.
429 Too many requests. Wait before retrying.
503 Server error. Retry after a short wait; contact support if it persists.

06 End-to-end workflow

The typical integration flow is: upload your files → create a gallery → attach the files → share the payment link. The steps below show the complete sequence using cURL. Replace YOUR_KEY_HERE with your API key.

Step 1 — Upload a file

Call init to get a presigned S3 URL, PUT the file directly to S3, then call complete. Repeat for each file you want to sell.

1a. Init the upload

INIT=$(curl -sS -X POST "https://staging.snapselldev.eu/api/external/v1/uploads/simple/init" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_KEY_HERE" \
  -d '{
    "original_filename": "photo.png",
    "mime_type": "image/png",
    "file_size": 335872
  }')

UPLOAD_ID=$(echo $INIT | jq -r '.upload.id')
PUT_URL=$(echo $INIT | jq -r '.url')

1b. PUT the file directly to S3 (no auth header)

curl -sS -X PUT "$PUT_URL" \
  -H "Content-Type: image/png" \
  --upload-file /path/to/photo.png
# Returns empty 200 OK — the file is now on S3

1c. Complete the upload

curl -sS -X POST "https://staging.snapselldev.eu/api/external/v1/uploads/simple/$UPLOAD_ID/complete" \
  -H "Authorization: Bearer YOUR_KEY_HERE"
# upload.status will be "pending_processing" — preview generation is queued

Step 2 — Create a gallery

A gallery is the product your buyers purchase. Set a price and title, then add your files to it.

GALLERY=$(curl -sS -X POST "https://staging.snapselldev.eu/api/external/v1/galleries" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_KEY_HERE" \
  -d '{
    "title": "Summer pack",
    "price": 9.99,
    "description": "My summer collection"
  }')

GALLERY_HASH=$(echo $GALLERY | jq -r '.gallery.hash')

Step 3 — Add uploads to the gallery

Pass one or more upload_id values collected from step 1. Each upload can only belong to one gallery.

curl -sS -X POST "https://staging.snapselldev.eu/api/external/v1/galleries/$GALLERY_HASH/items" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_KEY_HERE" \
  -d "{\"upload_ids\": [$UPLOAD_ID]}"

Step 4 — Share the payment link

The payment_url returned in any gallery response is the checkout page for your buyers. You can also retrieve it any time by fetching the gallery.

curl -sS "https://staging.snapselldev.eu/api/external/v1/galleries/$GALLERY_HASH" \
  -H "Authorization: Bearer YOUR_KEY_HERE" \
| jq -r '.payment_url'
# https://staging.snapselldev.eu/buy/8pmTfurIG3

Other useful commands

List all your galleries (page 1)

curl -sS "https://staging.snapselldev.eu/api/external/v1/galleries" \
  -H "Authorization: Bearer YOUR_KEY_HERE"

List galleries — page 2, 30 per page

curl -sS "https://staging.snapselldev.eu/api/external/v1/galleries?page=2&per_page=30" \
  -H "Authorization: Bearer YOUR_KEY_HERE"

Delete a gallery

curl -sS -X DELETE "https://staging.snapselldev.eu/api/external/v1/galleries/GALLERY_HASH" \
  -H "Authorization: Bearer YOUR_KEY_HERE"

07 Support

Need a key or webhook URL?

For integration help:

support@snapsell.com

← Back to SnapSell

SnapSell
Registrieren

Developer Reference

API-Dokumentation

Diese Dokumentation ist nur auf Englisch verfügbar. Bitte wechsle zur englischen Sprachversion, um die vollständige API-Referenz zu lesen.