Ticketing partners
This guide is for external ticketing platforms that sell access to a Handbid auction and need to enroll each ticket buyer as a Handbid guest — for example, a box-office or RSVP tool integrating on behalf of one of our organizations. Available since Release 18.
One call — POST /v3/auction/{auctionId}/guests/register — replaces the
create-then-check-in pair an integration might otherwise need. Each ticket buyer
who opts in becomes a registered bidder with a paddle, and the Handbid welcome
email is queued for them.
Authentication
Section titled “Authentication”You get an organization API token instead of a manager login. Use it directly as the bearer on every call — there is no exchange step and no expiry to manage.
Authorization: Bearer <organization API token>| Environment | Host |
|---|---|
| Sandbox | qa-rest.handbid.dev |
| Production | rest.hand.bid |
Tokens differ per host, and a token only works on auctions belonging to your
organization. A user OAuth2 bearer (the kind minted by the
SMS-PIN flow) is never accepted here and is
refused 401 — this route only accepts a raw organization API token.
Register a ticket buyer
Section titled “Register a ticket buyer”POST https://<host>/v3/auction/<auctionId>/guests/registerAuthorization: Bearer <organization API token>Content-Type: application/json{ "idempotencyKey": "acme:ticket:AC-2027-000123", "email": "buyer@example.com", "firstName": "Jane", "lastName": "Doe", "phone": "3055551234", "phoneCountry": "US", "externalRef": { "provider": "acme", "orderId": "AC-2027-000100", "ticketId": "AC-2027-000123" }}| Field | Required | Notes |
|---|---|---|
auctionId (path) | Yes | The Handbid auction id for the event. |
idempotencyKey | Yes | Up to 64 characters, letters/digits/:/_/-/.. Using your own ticket id (prefixed with your provider name, as above) is the recommended pattern — same ticket, same key, every time. |
email | Yes | Up to 128 characters. |
firstName, lastName | Yes | Up to 64 characters each. |
phone | No | Whatever the buyer typed. Leave it out if they gave nothing. |
phoneCountry | No | Two-letter ISO 3166-1 country code. Defaults to US. |
externalRef | No | Your own ids (provider, orderId, ticketId), stored for reconciliation. |
Send only the fields listed here — unknown fields are rejected with a 400 so
typos surface immediately. Booleans must be JSON true/false, never strings.
A 200 response:
{ "outcome": "created", "guid": "3f2a1c9e-...", "aurId": 6431510, "userId": 3214094, "paddleId": 118, "state": { "isBidder": 1, "isCheckedin": 0, "participationStatusId": 1, "isDeleted": 0 }, "phone": { "accepted": true, "e164": "+13055551234", "reason": null }, "welcome": "published", "checkedInAt": null, "archived": false}outcome is created (new Handbid account, welcome email queued), linked
(the buyer already had a Handbid account, or was already registered on this
auction — same paddle, no second welcome email), or alreadyApplied (this key
was already seen; nothing changed — the response then also carries
originalOutcome, and phone.e164 is null on a replay because it is only
echoed on the first write).
welcome tells you where the email stands: pending/published = queued (not
yet delivered), sent = accepted by our mail provider, failed/unknown = we
could not confirm delivery (resolved = a Handbid operator closed it without
resending — we never resend automatically, so a buyer never gets two welcome
emails from us). archived is true if you replay a key after the auction has
been archived; ids still come back, and state is null once archiving has
completed.
The idempotency contract
Section titled “The idempotency contract”Ticket webhooks are frequently delivered more than once, and your own retries fire after timeouts. Both are safe:
- Same key again →
200withoutcome: "alreadyApplied"and the original identifiers. No second bidder, no second paddle, no second email. - Same key with a different
email, a different flag (registerBidder/sendWelcome/checkIn), or a differentexternalRef.ticketId→409 conflict. Two different tickets are sharing an id on your side — do not retry; mint a new key. - Same key with a different name, phone, country, or
externalRef.orderId→ treated as a replay; the values from the first call stand (first-write-wins). - Two identical calls arriving at the same instant — the second may get
409 conflictwithretryAfter: 2and the messageregistration in progress. Retry with the same key after two seconds and you getalreadyApplied.
Retrying is always safe with the same key — no status code can ever cause a second registration.
| When | Retry with the same key? |
|---|---|
| Timeout / network error | Yes |
Any 5xx, or 503 | Yes, after retryAfter |
429 | Yes, after retryAfter |
409 with retryAfter (“registration in progress”) | Yes, after retryAfter |
400, 401, 403, 404 | No — fix the request or contact Handbid |
409 without retryAfter (different email/flag/ticket) | No — mint a new key |
This is the recommended integration pattern, not just a failure fallback: replay the same call with the same key whenever you’re unsure whether the first one landed.
Phone numbers
Section titled “Phone numbers”We normalize — you do not need to validate.
- Send
phoneas typed andphoneCountryfrom your own country list. We parse the number as a national number of that country. A number starting with+is taken as international and overrides the country. - US and Canada: a bare 10-digit number is fine.
- If we cannot make a real mobile number out of it, the buyer is still
registered — we just store no phone. The response tells you:
phone.accepted: falsewithreasonof"invalid","placeholder", or"absent". - A bad
phoneCountryvalue (not a two-letter ISO code, or not a recognized one) is the one phone-related error that fails the whole call:400.
Optional flags
Section titled “Optional flags”All three default to the standard registration behavior:
registerBidder: false— add the person to the guest list without registering them as a bidder (no paddle, no email).sendWelcome: false— skip the welcome email. Only meaningful when a new account is created; linking to an existing account never sends a second welcome email regardless of this flag.checkIn: true— also mark the guest checked in. Not needed for a purely-online auction.
These flags are part of the idempotency key’s fingerprint — replaying a ticket
with different flag values is a 409 conflict, not a silent change.
Errors
Section titled “Errors”Plain JSON { "error": "...", "message": "..." }, per the site-wide
error envelope.
| Status | error | Meaning | Retry same key? |
|---|---|---|---|
| 400 | validation_error | Missing, invalid, or unknown field — message says which | Fix first |
| 401 | unauthorized | Token missing, unknown, revoked, or a user OAuth2 bearer | No — contact Handbid |
| 403 | forbidden | Token isn’t for this auction’s organization; partner registration isn’t enabled for the organization; or the organization’s bidder limit is reached | No — contact Handbid |
| 404 | not_found | Unknown auction id, or the auction has been archived (new key only) | No |
| 409 | conflict | Key reused with a different email/flag/ticket (do not retry), or a concurrent registration still in flight (retryAfter, retry) | See message |
| 429 | rate_limited | Slow down — 120 requests/minute per organization token; retryAfter seconds | Yes, after retryAfter |
| 503 | unavailable | Transient; retryAfter seconds | Yes, after retryAfter |
| 5xx | server_error | Ours | Yes |
Sandbox checklist
Section titled “Sandbox checklist”- Get your sandbox organization API token and a sandbox auction id from your Handbid contact.
- Register one test buyer, register the same payload again, and confirm you
get back
alreadyApplied. - Send one request with a garbage phone number and one with an international
number plus its
phoneCountry— check thephoneblock on each response. - Send one request with a typo’d field name and confirm the
400names it. - Tell your Handbid contact the ticket id format you plan to use as
idempotencyKey, so they can confirm it fits within 64 characters.
Full request reference
Section titled “Full request reference”The complete field list, validation rules, and JSON examples for every
response code live on the API reference page:
POST /v3/auction/{auctionId}/guests/register.