Seatpin Seller API Documentation

Version: v1
Base URL: https://seller.seatpin.com/api/v1
Portal URL: https://seller.seatpin.com
Last Updated: 2026-07-24


Table of Contents

  1. Overview & Quick Start
  2. Authentication
  3. Base URL & Environments
  4. Rate Limiting
  5. IP Whitelisting
  6. Response Format & Error Codes
  7. Endpoints
  8. Webhooks

1. Overview & Quick Start

What is the Seller API?

The Seatpin Seller API is a REST API that gives approved high-volume ticket sellers programmatic access to the Seatpin marketplace. It allows you to manage your entire inventory lifecycle, track sales and fulfilment, and pull detailed performance reports — all without using the Seatpin seller web interface.

Use Cases

  • Inventory automation — sync listings from your own inventory system into Seatpin in bulk, update prices in real time, publish/unpublish on demand
  • Sales fulfilment — poll for new orders, retrieve buyer PII (proportional to ticket format requirements), upload e-tickets or tracking numbers
  • Performance reporting — aggregate revenue, order counts, and per-listing metrics grouped by day/week/month/year
  • Webhook-driven workflows — receive push notifications to your own server the moment a listing or order changes state

Onboarding Flow

  1. Contact Seatpin support to request Seller status for your account.
  2. Once approved, log in at https://seller.seatpin.com with your existing Seatpin credentials.
  3. Complete 2FA setup (TOTP app required — this is mandatory before API keys can be created).
  4. Navigate to Portal → API Keys → Create new key. Copy the token immediately — it is shown only once.
  5. Optionally add your server IPs under Portal → IP Whitelist to restrict which machines can call the API.
  6. Optionally configure webhook endpoints under Portal → Webhooks to receive push events.
  7. Start making API calls using Authorization: Bearer {your_token}.

Postman & Bruno Collections

Ready-to-run API collections are available so you can start calling the API without hand-writing requests. Every endpoint in this document — including the Sandbox Order Simulation endpoints — is included, with example bodies and saved response examples.

⬇ Download collections (.zip) — contains both the Postman collection and the Bruno collection, plus ready-made Sandbox and Production environment files.

Postman

  1. Unzip and open Postman → Import → drop in postman/Seatpin-PowerSeller-API.postman_collection.json.
  2. Import the environment for the target stage: postman/Seatpin-PowerSeller-API.postman_environment_sandbox.json (recommended to start) or ..._production.json.
  3. Select the imported environment (top-right), then set the api_key variable to the token from Portal → API Keys.
  4. The sandbox request Create Sandbox Order auto-saves the returned order_id into the sandbox_order_id variable, so Pay / Cancel / Refund run against it with no manual copy-paste.

Bruno

  1. Unzip and open Bruno → Open Collection → select the bruno/ folder.
  2. Pick the sandbox or production environment (top-right), then set the secret api_key value.
  3. Same as Postman, 05 Sandbox → Create Order stores the new order_id for the follow-up lifecycle requests.

There is no separate OpenAPI/Swagger spec file — this document plus the Postman/Bruno collections are the source of truth for the request/response contract. The collections are kept in sync with the API on every release.


2. Authentication

All API endpoints (under /api/v1/...) require a Bearer token issued from the Seller Portal.

Token format: {id}|{random_secret} (Laravel Sanctum personal access token with the power-seller ability).

Creating a key: Portal → API Keys → Create new key. The plaintext token is displayed only once; store it securely (e.g., a secrets manager).

Revoking a key: Portal → API Keys → Revoke. After revocation, all API calls using that key return 401 Unauthorized.

Multiple keys: You may create multiple API keys (e.g., one per integration or environment). Each key can be revoked independently.

Security note: Treat your API key like a password. Never commit it to version control. Use environment variables or a secrets vault.


3. Base URL & Environments

Environment Base URL Notes
Production https://seller.seatpin.com/api/v1 Live marketplace data
Sandbox https://sandbox-seller.seatpin.com/api/v1 Reduced-scope mirror of production for integration work

The portal (2FA, API keys, webhooks, public docs) is served at the root of the same domain: https://seller.seatpin.com for production, https://sandbox-seller.seatpin.com for sandbox.

Sandbox vs Production

Sandbox is identical to production at the API contract level — same request and response shapes, same auth, same error envelope — but the dataset surface is capped so integrators can safely test against a small, realistic slice:

  • /events/search returns at most 10 events (the nearest upcoming ones), regardless of the requested per_page.
  • The remaining endpoints behave normally but only see your sandbox account's listings, orders and webhooks.
  • Sandbox additionally exposes the order simulation endpoints (/sandbox/orders/..., see 7.5) so you can place a test order on your own listing and receive the real order.* / payment.* webhooks end-to-end. These endpoints return 404 on production.

Build your integration against sandbox first; promote to production once the contract is wired up. Token, IP whitelist, and webhook configurations are independent between the two environments — set them up separately in each portal.


4. Rate Limiting

  • Default limit: 180 requests per minute per API key
  • Exceeded: 429 Too Many Requests with a Retry-After header (seconds until the window resets)

Best practice: implement exponential back-off in your client, and check the Retry-After header before retrying.


5. IP Whitelisting

IP whitelisting is optional. When no IPs are configured for your account, requests from any IP address are accepted.

Once you add one or more IPs:

  • Only requests originating from those IPs are accepted
  • Other IPs receive 403 Forbidden

Managing IPs: Portal → IP Whitelist → Add / Remove
IPv4 and IPv6 are both supported (addresses are normalised internally via inet_pton).
Reverse proxy environments: The API trusts X-Forwarded-For headers according to the server's TrustProxies configuration.


6. Response Format & Error Codes

Success Envelope

meta and links are only present on paginated endpoints. Non-paginated responses return data (and sometimes message).

Error Envelope

Every non-2xx response shares the same shape:

  • success is always false for error responses.
  • message is a short human-readable summary suitable to surface to your end users.
  • errors is an object keyed by field name for 422 Unprocessable Entity validation failures. For all other status codes it is an empty object ({}).

Internal details (SQL errors, table names, file paths, stack traces) are stripped in production — clients see only the safe message. Enable APP_DEBUG=true in non-production environments if you need raw exception text while integrating.

HTTP Status Codes

Code When Notes
200 OK Successful GET or POST
201 Created Resource(s) created Returned by bulk-add when all listings succeed
207 Multi-Status Partial success bulk-add with some failures; bulk-update-action with some failures
400 Bad Request Malformed JSON body
401 Unauthorized Missing or invalid Bearer token
403 Forbidden Token valid but account not a Seller, or IP not whitelisted
404 Not Found Resource missing, or exists but owned by a different user message is endpoint-specific (e.g. Order not found., Listing not found.)
405 Method Not Allowed Wrong HTTP verb for this endpoint
422 Unprocessable Entity Validation failure — errors object present, or bulk-add/bulk-update-action where every item failed
429 Too Many Requests Rate limit exceeded — Retry-After header present
500 Internal Server Error Unexpected server error — contact support Internal details are not exposed in production

7. Endpoints

7.1 Events

GET /api/v1/events/search

Search for upcoming events available on the Seatpin marketplace. Only active, visible events with a future date are returned. Results are ordered by event date ascending.

Authentication: Bearer token required.

Query Parameters:

Parameter Type Required Description
event_name string no Partial match on event name (translated)
performer_name no no Partial match on performer/artist name
venue_name string no Partial match on venue name
start_date date (YYYY-MM-DD) no Only events on or after this date
end_date date (YYYY-MM-DD) no Only events on or before this date. Must be >= start_date
per_page integer no Results per page. Default: 20, max: 50
page integer no Page number. Default: 1

Request:

Response 200:

The url field is the public Seatpin storefront page for the event (the same page a buyer sees). It is built from the event's English slug; null on the rare event that has no slug yet. The same field is returned by /events/details.

Error Responses:

  • 401 — Missing or invalid token
  • 422 — Invalid query parameter (e.g., end_date before start_date)

GET /api/v1/events/details

Retrieve full details for one or more events by ID, including venue information and the complete map category/section tree needed to create listings.

Authentication: Bearer token required.

Query Parameters:

Parameter Type Required Description
event_id integer or array yes (at least one) One or more event IDs. Up to 50 per request.

The API normalizes all of the following shapes into an integer array:

?event_id[]=1042                          ← single value (array form)
?event_id[]=1042&event_id[]=1055          ← standard repeated query param
?event_id=1042,1055                       ← comma-separated string
?event_id=[1042,1055]                     ← bracketed string

Request:

Response 200:

Notes:

  • Use the category.id as the category field and section.id as the section field when creating listings via /api/v1/inventory/bulk-add.
  • sections at the top level is a flat list of all sections across all categories — a convenience for UIs that need a single lookup list.
  • Events that are not active, hidden, or not published on Seatpin are silently omitted from the response.

Error Responses:

  • 401 — Missing or invalid token
  • 422event_id param missing or not an array

7.2 Inventory

POST /api/v1/inventory/bulk-add

Add one or more new listings to your inventory in a single request. A maximum of 100 listings can be submitted per call.

Authentication: Bearer token required.

Headers:

Authorization: Bearer {api_key}
Content-Type: application/json

Request Body:

Note: You do not send a listing_id when creating listings. Seatpin generates a stable, immutable 5–11 digit numeric listing_id for each successful row and returns it in the response. Use that value for every subsequent call (update, bulk-update-action, list/get, etc.).

Field Type Required Description
publish integer (0 or 1) yes 1 = publish listings immediately; 0 = save as draft
listings array yes Array of listing objects. Min 1, max 100.
listings[].event_id integer yes Seatpin event ID (from /events/search or /events/details)
listings[].category integer no Category ID from event.categories[].id
listings[].section integer no Section ID from event.categories[].sections[].id
listings[].row string no Row identifier (e.g. "A", "12"). Max 50 chars.
listings[].quantity_available integer yes Number of tickets in this listing. Min 1.
listings[].split_type string no One of Separately, All in one, Dont leave one, Single seats, Sell in multiples. Defaults to Separately.
listings[].split_quantity integer conditional Only honoured when split_type is "Sell in multiples". Every other split type is stored with split_quantity = 1. Defaults to 2 when Sell in multiples is used without an explicit quantity.
listings[].sell_price string (decimal) yes Per-ticket seller price — what you receive per ticket (e.g. "125.00"). The buyer-facing price is computed and stored separately.
listings[].sell_price_currency string yes ISO currency code. Must be one of the external_id values returned by /inventory/options.currencies.
listings[].ticket_type integer no external_id from /inventory/options.ticket_types
listings[].in_hand integer (0 or 1) no 1 = tickets are physically in hand
listings[].restrictions array of integers no external_ids from /inventory/options.restrictions
listings[].benefits array of integers no external_ids from /inventory/options.benefits

Delivery There is no separate delivery_options field. Delivery is implied by ticket_type: file-based formats (E-tickets) are fulfilled by uploading files, mobile / mobile-link formats are fulfilled by sending ticket links (mobile_link[] / ios_link[] / android_link[]), and paper-ticket orders expose a buyer_address in /sales/details for physical shipping. All are submitted through /sales/fulfilment.

Request:

Response 201 (all succeeded):

Response 207 (partial success — at least one created, at least one failed):

Response 422 (every row failed):

Error Responses:

  • 401 — Invalid or missing token
  • 422 — Top-level validation failed (e.g. event_id does not exist, missing required fields) or every listing in the batch failed

GET /api/v1/inventory/options

Returns all valid values for enumerated listing fields. Use this endpoint to populate dropdown menus in your integration UI.

Authentication: Bearer token required.

Request:

Response 200 (truncated):

About the values

  • ticket_types, benefits, restrictions and currencies are pulled from the live database — call this endpoint periodically to pick up new options.
  • external_id for ticket_types, benefits and restrictions is an integer (rows in the listing_formats, advantages, and requirements tables respectively).
  • external_id for currencies is the ISO code from the currencies table (e.g. "GBP"). This is the exact value you must send in sell_price_currency.
  • split_types is a fixed vocabulary and uses string identifiers.

POST /api/v1/inventory/update

Update a single listing by listing_id. Only the fields you include are updated — omitted fields are left unchanged. Ownership is verified; you cannot update another seller's listing.

Authentication: Bearer token required.

Headers:

Authorization: Bearer {api_key}
Content-Type: application/json

Request Body:

Field Type Required Description
listing_id string yes The 5–11 digit numeric identifier returned at create time (immutable)
event_id integer no Change the event (use with care)
category integer no New category ID
section integer no New section ID
row string no New row
quantity_available integer no New ticket quantity (1–1000)
split_type string no Separately, All in one, Dont leave one, Single seats, Sell in multiples. Sending any value other than Sell in multiples automatically resets split_quantity to 1.
split_quantity integer conditional Only persisted when the listing's effective split_type is Sell in multiples. Otherwise ignored.
sell_price string (decimal) no New per-ticket seller price. Buyer price is recomputed automatically as sell_price * (1 + seller_commission_rate).
sell_price_currency string no ISO currency code from /inventory/options.currencies (e.g. "GBP")
ticket_type integer no external_id from /inventory/options.ticket_types (writes to listing_format_id)
benefits array of integers no external_ids from /inventory/options.benefits (replaces existing set)
restrictions array of integers no external_ids from /inventory/options.restrictions (replaces existing set)
in_hand integer (0/1) no Whether tickets are in hand
publish integer (0/1) no Publish (1) or unpublish (0)

Request:

Response 200:

Error Responses:

  • 401 — Invalid or missing token
  • 404 — Listing not found or does not belong to you
  • 422 — Validation failed

POST /api/v1/inventory/bulk-update

Perform a bulk status action (publish all, unpublish all, or delete) on multiple listings at once. Accepts multipart form data (matching the Tixstock pattern for compatibility).

Authentication: Bearer token required.

Content-Type: multipart/form-data

Form Fields:

Field Type Required Description
action string yes publish-all, unpublish-all, or delete
listing_ids string or array conditional 5–11 digit listing IDs. Accepts comma-separated string (e.g. "82691538,35283569") or listing_ids[] array form.

If no IDs are provided with publish-all or unpublish-all, the action applies to all of your listings. With delete, listing_ids is required.

Request:

Response 200:

Error Responses:

  • 401 — Invalid or missing token
  • 422 — Unknown action, or delete called without IDs

POST /api/v1/inventory/bulk-update-action

Update multiple listings with individual field changes in a single request. Supports per-listing field overrides. Returns 207 Multi-Status when some listings fail.

Authentication: Bearer token required.

Headers:

Authorization: Bearer {api_key}
Content-Type: application/json

Request Body:

Field Type Required Description
publish integer (0/1) no Global publish override applied to all listings in this call
listings array yes Array of listing update objects (same fields as /inventory/update)
listings[].listing_id string yes The 5–11 digit numeric listing identifier returned at create time
listings[].sell_price string (decimal) no New price
listings[].sell_price_currency string no New currency
listings[].quantity_available integer no New quantity
listings[].split_type string no Separately, All in one, Dont leave one, Single seats, Sell in multiples
listings[].ticket_type integer no external_id from /inventory/options.ticket_types
listings[].publish integer (0/1) no Per-listing publish override (overridden by global publish if set)
(any other update field) no See /inventory/update field table

Request:

Response 200 (all succeeded):

The payload map echoes listing_id → listing_id to confirm that each requested update was applied to the correct row. is_ticket_type_change flags whether the listing's ticket_type was modified (1) or unchanged (0).

Response 207 (partial failure):

Response 422 (every row failed):


POST /api/v1/inventory/get

Retrieve a paginated list of events that have at least one active listing belonging to you.

Authentication: Bearer token required.

Headers:

Authorization: Bearer {api_key}
Content-Type: application/json

Request Body:

Field Type Required Description
event_name string no Partial match on event name
start_date date (YYYY-MM-DD) no Filter events on or after this date
end_date date (YYYY-MM-DD) no Filter events on or before this date
per_page integer no Results per page. Default: 20
page integer no Page number. Default: 1

Request:

Response 200:


POST /api/v1/inventory/event/{event}/lists

Get a paginated list of your listings for a specific event. The {event} path parameter is the Seatpin event ID.

Authentication: Bearer token required.

Path Parameters:

Parameter Type Description
event integer Seatpin event ID

Query Parameters:

Parameter Type Required Description
per_page integer no Default: 20
page integer no Default: 1

Body Parameters:

Field Type Required Description
status string no Filter by status: active, inactive, pending
category_id integer no Filter by map category ID

Request:

Response 200:

Error Responses:

  • 404 — Event not found, not active, or not published on Seatpin

GET /api/v1/inventory/list/get

Retrieve a single listing by its listing_id.

Authentication: Bearer token required.

Query Parameters:

Parameter Type Required Description
listing_id string yes The 5–11 digit numeric identifier returned at create time

Request:

Response 200:

Reading back your edits ticket_type, split_type, split_quantity, benefits and restrictions are returned in exactly the shapes the write endpoints accept, so a read directly confirms whether an edit propagated. Note the pricing asymmetry: face_value in read responses is the seller price you submitted as sell_price at write time; the read-side sell_price is the buyer-facing price (face_value * (1 + seller_commission_rate)).

Error Responses:

  • 404 — Listing not found or does not belong to you
  • 422listing_id is missing or not in the 5–11 digit numeric format

POST /api/v1/inventory/counts

Get aggregate listing counts for your account. Optionally scoped to a specific event.

Authentication: Bearer token required.

Request Body:

Field Type Required Description
event_id integer no Scope counts to a specific event

Request:

Response 200:

Field Definitions:

Field Description
total All listings including deleted
published Active (is_active=1), approved (approval=1), not deleted
unpublished Inactive (is_active=0), not deleted
deleted Soft-deleted listings
pending_approval Not yet approved by Seatpin admin, not deleted

7.3 Sales

POST /api/v1/sales/listing

Retrieve a paginated list of your sales (orders placed against your listings).

Authentication: Bearer token required.

Request Body:

Field Type Required Description
date_from date (YYYY-MM-DD) no Include orders created on or after this date
date_to date (YYYY-MM-DD) no Include orders created on or before this date. Must be >= date_from
status string no Filter by derived order status: pending, approved, cancelled, refunded, fulfilled. (confirmed and completed are accepted as legacy synonyms of approved / fulfilled.)
per_page integer no Default: 20, max: 100
page integer no Default: 1

Request:

Response 200:

About order_id: This is the public-facing order identifier — a string of ~10 digits (Seatpin's order_code, e.g. "4820916377"). The internal numeric primary key is never exposed by this API. The same value arrives in the order.created / payment.confirmed webhooks, and it is the exact value you pass to /sales/details and /sales/fulfilment.


POST /api/v1/sales/details

Retrieve full details for a single order including buyer PII appropriate to the ticket format.

Authentication: Bearer token required.

Request Body:

Field Type Required Description
order_id string yes The order code returned by /sales/listing (e.g. "4820916377")

PII Matrix by Ticket Format:

Format PII Fields Returned
eticket / default first_name, last_name
mobile first_name, last_name, email
pdf first_name, last_name
paper-ticket / paper-ticket-lms name (full name) + top-level buyer_address object
passolig-tickets / passolig-tr-tickets name, nationality, national_id, birth_place, dob
Italy football events (any format) Same as passolig above

Request:

Response 200 (e-ticket order — captured from a live order, customer names anonymised):

Field notes

  • The customer object is nested inside each ticket_details[] entry — one entry per ticket. When buyer names have not been collected yet, customer is null (the entry itself is still present).
  • status is always populated and derived from the real order state — see Order Lifecycle, Statuses & When to Act.
  • listing.category / section / row / ticket_format come from the order, not the listing: they are snapshotted at sale time and stay fixed for the lifetime of the order, even if the listing is later edited or deleted. Clients get exactly what the buyer purchased.

Response 200 (paper ticket order — includes top-level buyer_address):

Error Responses:

  • 401 — Invalid or missing token
  • 404 — Order not found or does not belong to you

POST /api/v1/sales/fulfilment

Deliver an order to the buyer. You can fulfil in one of three ways: upload ticket files, provide mobile ticket links (for mobile / mobile-link formats), or supply shipping tracking for physical tickets. Accepts multipart form data.

Files are stored in S3 private storage at Seller-tickets/{user_id}/{order_id}/{uuid}.{ext}. After submitting, a Seatpin admin manually reviews and confirms delivery — is_sent and is_delivered flags are not automatically set.

Authentication: Bearer token required.

Content-Type: multipart/form-data

Form Fields:

Field Type Required Description
order_id string yes The order code returned by /sales/listing (e.g. "4820916377")
files[] file no* Ticket file(s) to upload. Accepted formats: PDF, JPG, PNG. Max 10 files, 10 MB each.
mobile_link[] url no* Universal mobile ticket links (one URL that opens on any device). Max 10, each a valid URL up to 2048 chars.
ios_link[] url no* iOS-specific ticket links (e.g. Apple Wallet). Max 10, each a valid URL up to 2048 chars.
android_link[] url no* Android-specific ticket links (e.g. Google Wallet). Max 10, each a valid URL up to 2048 chars.
tracking_number string no* Shipping tracking number (max 191 chars)
shipping_company string no* Shipping company name (max 100 chars). Required when tracking_number is provided.

*At least one delivery method must be provided: files[], or any of the mobile link arrays (mobile_link[] / ios_link[] / android_link[]), or both tracking_number and shipping_company. Providing only tracking_number without shipping_company (or vice versa) returns a validation error.

How mobile links map to tickets: the link arrays are distributed 1:1 by index to the individual tickets in the order. mobile_link[0], ios_link[0] and android_link[0] all apply to the first ticket, [1] to the second, and so on — so you can attach a universal link, an iOS link and an Android link to the same ticket at once. Send links in the same order the tickets appear in /sales/details. Blank array entries are skipped, so you can target specific tickets by leaving earlier slots empty (e.g. mobile_link[0]=""&mobile_link[1]=https://...).

Request (file upload):

Request (mobile ticket links):

Request (shipping tracking):

Response 200:

Error Responses:

  • 401 — Invalid or missing token
  • 404 — Order not found or does not belong to you
  • 422 — No delivery method provided (no files, no mobile links, no tracking details); or cancelled/refunded order
  • 422 — File too large, wrong format, or too many files; or a mobile link is not a valid URL / exceeds 2048 chars

GET /api/v1/sales/sales-options

Returns all valid values for order status and shipping fields. Use to populate dropdowns in your integration.

Authentication: Bearer token required.

Request:

Response 200:


7.4 Reports

POST /api/v1/reports/details

Retrieve a sales summary report grouped by a time period (day/week/month/year). Dates are timezone-aware using the server's configured timezone.

Authentication: Bearer token required.

Request Body:

Field Type Required Description
period string yes Grouping granularity: day, week, month, or year
start_date date (YYYY-MM-DD) no Report start date. Default: 30 days ago. Max range: 365 days.
end_date date (YYYY-MM-DD) no Report end date. Default: today
event_id integer no Scope report to a specific event

Request:

Response 200:

Period Format by Granularity:

Period Format Example
day YYYY-MM-DD 2026-06-09
week YYYY-WW (ISO week) 2026-24
month YYYY-MM 2026-06
year YYYY 2026

Notes:

  • Refunded orders are excluded from all revenue calculations.
  • currency in the summary is "mixed" when your orders span multiple currencies.

POST /api/v1/reports/listing

Retrieve a paginated per-listing performance report. Useful for identifying your top-performing listings.

Authentication: Bearer token required.

Request Body:

Field Type Required Description
event_id integer no Scope to a specific event
start_date date (YYYY-MM-DD) no Default: 90 days ago
end_date date (YYYY-MM-DD) no Default: today
per_page integer no Default: 20
page integer no Default: 1
sort_by string no Sort field: revenue (default), tickets_sold, created_at
sort_direction string no asc or desc (default: desc)

Request:

Response 200:


GET /api/v1/reports/reports-options

Returns all valid values for report configuration fields.

Authentication: Bearer token required.

Request:

Response 200:


7.5 Sandbox Order Simulation

Sandbox only. Every endpoint in this section returns 404 unless the environment runs in sandbox mode.

Place a test order against your own published listing and walk it through the full lifecycle. Each step writes real rows and fires the exact same observers as a storefront sale, so:

  • the real order.created, order.updated, order.cancelled, payment.confirmed and payment.refunded webhooks are dispatched to your configured endpoints (inspect request/response in Portal -> Webhook Logs);
  • the order shows up in POST /sales/listing and POST /sales/details with the exact production payload shapes (including nested ticket_details[].customer objects, populated with fake buyer names).

Typical flow:

POST /api/v1/sandbox/orders                      -> (no webhook yet)         (status: pending)
POST /api/v1/sandbox/orders/{order_id}/pay      -> order.created + payment.confirmed + order.updated (status: approved)
POST /api/v1/sandbox/orders/{order_id}/cancel   -> order.cancelled          (status: cancelled)
POST /api/v1/sandbox/orders/{order_id}/refund   -> payment.refunded + order.updated (status: refunded)

order.created mirrors production semantics: it fires when the payment reaches step-1 approval (the /pay step), not when the order row is first created.

POST /api/v1/sandbox/orders

Field Type Required Description
listing_id string yes Your listing's 5-11 digit identifier. Must be published (publish=1) and belong to you.
quantity integer no Tickets to "buy". Default 1, max 10, cannot exceed the listing's availability.

Response 201:

Use the returned order_id directly with POST /sales/details to capture the full order payload.

POST /api/v1/sandbox/orders/{order_id}/pay

Creates a payment and advances it to step-1 approval in one call. Fires order.created, payment.confirmed and order.updated (status approved). Returns 422 if already paid.

POST /api/v1/sandbox/orders/{order_id}/cancel

Sets the order status to cancelled. Fires order.cancelled. Returns 422 if already cancelled.

POST /api/v1/sandbox/orders/{order_id}/refund

Requires a confirmed payment (/pay first). Marks the full amount refunded — fires payment.refunded and order.updated (status refunded). Returns 422 if not paid yet or already refunded.

Notes

  • {order_id} is the public order code returned at creation; these endpoints only ever operate on orders created via /sandbox/orders — real orders cannot be touched.
  • Sandbox orders are purged automatically after a retention period (default 7 days).

8. Webhooks

Webhooks allow Seatpin to push real-time event notifications to your server. You configure one or more webhook endpoints in the Portal, and Seatpin sends an HTTP POST to your URL whenever a subscribed event occurs.

Configuring Webhooks

  1. Go to Portal → Webhooks → Add webhook
  2. Enter your endpoint URL (must be a publicly reachable HTTPS URL)
  3. Select the event types you want to receive
  4. Save — the secret is shown once; copy it immediately
  5. To rotate a secret: Portal → Webhooks → Regenerate secret

Limits: Up to 20 webhook endpoints per account (configurable via Seller_WEBHOOK_MAX_PER_USER).

Delivery Format

All webhooks are sent as HTTP POST with the following headers:

Content-Type: application/json
User-Agent: Seatpin-Seller-Webhook/1.0
X-Seller-Event: listing.created
X-Seller-Signature: sha256=<hmac_hex>
X-Seller-Timestamp: 1749466800
X-Seller-Delivery: 9821

Body envelope:

Verifying Signatures

The X-Seller-Signature header is sha256= followed by the hex HMAC-SHA256 of the raw request body using your webhook secret.

Always verify signatures to ensure requests are genuinely from Seatpin and have not been tampered with.

Node.js:

Python:

PHP:

Important: Always read rawBody before JSON-parsing. Many frameworks consume the body stream during parsing, leaving an empty string for HMAC calculation.

Order Lifecycle, Statuses & When to Act

data.status (and the status in /sales/listing + /sales/details) is derived from the real order state and is always populated:

status meaning
pending Payment received but not yet verified by Seatpin
approved Payment verified (step-1 approval) — safe to register the order and deliver tickets
fulfilled Tickets delivered to the buyer
cancelled Payment declined or order cancelled
refunded Refund confirmed

When to act:

  • Treat order.created as "the sale is confirmed" — it fires exactly once per order, at the moment the order reaches approved. Register the order and start fulfilment on this event.
  • payment.confirmed may fire more than once (each approval step); it is informational — you do not need to act on it if you key off order.created.
  • order.updated fires on later state changes — read data.status from it.
  • Cancellations and refunds have dedicated events: order.cancelled (payment declined or order cancelled) and payment.refunded (refund confirmed; the order's status also becomes refunded).

Event Types and Payload Schemas

listing.created

Fired when a new listing is created.

listing.updated

Fired when a listing's fields change.

listing.deleted

Fired when a listing is soft-deleted.

order.created

Fired when an order becomes confirmed for you — i.e. when its payment reaches step-1 approval (Seatpin verifies the buyer's payment). It is intentionally not fired at raw checkout time, so you never hear about orders whose payment was never verified. Sent once per order.

order_id is the same order code that POST /sales/details accepts — pass it straight through to fetch full order details (including buyer info). listing_id is the listing's public identifier. seller_price is the seller-side order total in seller_currency — the amount you will receive. The example below is the same order shown in the /sales/details example above.

order.updated

Fired when an order's fields change (except a change to cancelled, which fires order.cancelled instead). Same payload shape as order.createdstatus and updated_at carry the new values.

order.cancelled

Fired when an order's status changes to cancelled. Same payload shape as order.created.

ticket.hold

Fired when tickets are put on hold (reserved for a buyer during checkout).

ticket.release

Fired when a hold is released (checkout abandoned or timed out).

payment.confirmed

Fired whenever the payment advances through a confirmation step (payment received, or an approval step is completed). You may receive this event more than once per order — treat deliveries idempotently, keyed on order_id.

order_id is the same order code that POST /sales/details accepts. seller_price is the seller-side order total in seller_currency — the amount you will receive. The example below is the payment for the same order shown above.

payment.refunded

Fired when a refund is confirmed on the payment. Same base payload as payment.confirmed, plus refund_status.

webhook.test

Fired when you trigger a test delivery from the Portal.

Retry Policy

  • Delivery is considered successful when your endpoint responds with any 2xx HTTP status code.
  • If delivery fails, the system retries 3 times total with exponential back-off:
    • Attempt 1: immediate
    • Attempt 2: +60 seconds
    • Attempt 3: +300 seconds
    • Attempt 4 (final): +900 seconds
  • Auto-disable: After 10 consecutive delivery failures the webhook is automatically disabled, and a notification email is sent to your account email.
  • Re-enable: Portal → Webhooks → Edit → Enable. Fix the underlying issue before re-enabling.
  • Manual retry: Portal → Webhook Logs → select a failed delivery → Retry.
  • Timeout: Each delivery attempt has a 30-second timeout.

DNS Rebinding Protection

The webhook delivery system validates that your configured URL resolves to a public IP address at the time of each delivery attempt. URLs resolving to private RFC 1918 ranges (e.g., 192.168.x.x, 10.x.x.x) or loopback addresses are rejected and counted as a failure.