Skip to content

About

Official Go server SDK for SeatLayer reserved seating: seating charts, seat maps, events, holds, booking and webhook verification, with idempotency and retries built in.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

50 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SeatLayer Go Server SDK for Reserved Seating

CI Go Reference License: MIT

The official Go client for the SeatLayer API. This module lets a Go backend inspect seat holds, price orders from server data, book reserved seats and verify webhooks, with no dependencies beyond the standard library. SeatLayer is seating chart and reserved-seat ticketing software built for venues up to stadium scale.

SeatLayer's official Go server SDK is the trusted side of its reserved seating and seat booking API: inspect the holds a buyer created, price from server data, and book with a stable BookingRef. From Go you manage seating charts, events, sales channels, and live seat inventory through one typed ticketing API client.

SeatLayer module on pkg.go.dev · Go server SDK guide · SeatLayer developer platform · SeatLayer JavaScript seat map SDK · Server API reference

Server-side only. This package authenticates with your secret key. Never embed it in anything a ticket buyer can reach. Browser surfaces get short-lived, origin-bound tokens that you mint here.

Two-step shape: the buyer picks and holds seats in the client with your public key, then this SDK confirms the booking from your server with your secret key while your platform keeps checkout and its own payment provider.

Start here: Quickstart · Holds and checkout · Go server SDK guide · SDK catalog · Pricing: $0 entry, 100 free confirmed-sold-seat credits per organization each month, then $0.10 down to $0.05 a credit, and credits never expire.

Scale evidence

Benchmarked on public 100,000-, 150,000- and 200,000-seat venue fixtures on 15 September 2026: 200,000 seats chart-ready in 1.95 s, desktop, local production build. Fixtures, method and run logs: https://github.com/seatlayer/seatlayer-performance. Live 200,000-seat stadium demo: https://app.seatlayer.io/demo/play/century-stadium-200k. This is renderer evidence, not a concurrent-buyer claim.

Install the Go seat booking SDK

go get github.com/seatlayer/seatlayer-go@v0.9.0

The module resolves straight from this repository through the Go module proxy, so there is no registry account to create; v0.9.0 is the current release and the API reference is published on pkg.go.dev. Requires Go 1.23 or newer (for range-over-func iterators). No dependencies: standard library only.

Quick start

import (
    "context"
    "os"

    "github.com/seatlayer/seatlayer-go"
)

client, err := seatlayer.New(os.Getenv("SEATLAYER_SECRET_KEY"))
if err != nil {
    return err
}
ctx := context.Background()

// 1. Materialize a published catalog template as the organiser's draft chart.
// Replace this placeholder with a template id from your catalog.
chart, err := client.Templates.InstantiateTemplate(ctx, "your-published-template")
if err != nil {
    return err
}
chartID := chart["meta"].(map[string]any)["id"].(string)
if _, err := client.Charts.Publish(ctx, chartID); err != nil {
    return err
}

// 2. Create an event on it.
event, err := client.Events.Create(ctx, seatlayer.EventCreateParams{
    ChartID: chartID,
    Name:    "Spring Gala",
    Currency: "EUR",  // leave empty to inherit the workspace currency
    Region:  seatlayer.RegionWesternEurope, // India: RegionAsiaPacific
})
if err != nil {
    return err
}
eventKey := event["meta"].(map[string]any)["key"].(string)

// 3. Sell four seats over the phone.
held, err := client.Inventory.HoldBestAvailable(ctx, eventKey, seatlayer.BestAvailableParams{Qty: 4})
if err != nil {
    return err
}
// … take payment against held["items"], which carry authoritative prices …
_, err = client.Inventory.Book(ctx, eventKey, seatlayer.BookParams{
    HoldID:     held["holdId"].(string),
    BookingRef: "order-8842",
})

Event hosting region

Set EventCreateParams.Region based on the event venue, not your API server or office. It controls the initial placement of the Event's live inventory; an existing Event cannot be moved later. Leave it empty to inherit the workspace default (western-europe for new accounts). Set the default through WorkspaceCreateParams.DefaultRegion or UpdateWithParams(..., WorkspaceUpdateParams{DefaultRegion: ...}); updates affect only future Events.

  • western-europe, eastern-europe, north-america-east, north-america-west, south-america
  • asia-pacific, northeast-asia, southeast-asia, oceania, africa, middle-east

The hint is best effort, not a data-residency guarantee. See the full Event region guide.

For nullable event-create fields, ordinary scalar fields cover the common value-or-omit case. Use the Nullable overlay when the wire call must contain an explicit JSON null, for example Nullable: seatlayer.EventCreateNullableFields{Venue: seatlayer.FieldNull[string]()}.

Every method takes a context.Context. Cancelling it stops retries immediately rather than being treated as a transient fault to back off through.

Fixed Renewable Seasons

client.Seasons exposes 87 trusted organizer operations: the 48 added in v0.7.0, plus performances, package prices (per price type, with the SeasonPriceType… constants), one-call publishing, checkout tests, the holder list, Season order refunds and cancellations, hosted renewals, renewal quotes, intents and checkouts, sales and seat reports, CSV exports, and the bulk refund of a cancelled date (RefundPerformance) and release of lapsed offers (ReleaseLapsedOffers) with their jobs (ListBulkJobs, RetrieveBulkJob). Above 100 contracts CreateRenewalOffers answers 202 with a renewal_run job and its batchId, and above 100 holders CreateHolderImportFromSeason answers 202 with a holder_import job; follow either with RetrieveBulkJob. ListRenewalOffers and RetrieveHostedRenewals read one page at a time: pass SeasonOfferPageParams{Cursor: nextCursor}. ExportHoldersCSV and ExportOrdersCSV return the CSV text unchanged; DeletePlan answers 204 and returns only an error.

After the test hold/book/cancel journey and matching webhook deliveries, client.Seasons.ValidateBuyerRehearsal(ctx, seasonKey) sends no evidence body; SeatLayer discovers the retained chain automatically. Retrieved Season holds contain inventory identity, not an authoritative amount. Your platform owns package price, payment, order, tax, refunds, benefits, and ticket or pass delivery.

checked, err := client.Seasons.Validate(ctx, seatlayer.SeasonSelectionParams{
    SourcePerformanceGroupKeys: []string{"pg_subscription_run"},
})
created, err := client.Seasons.Create(ctx, seatlayer.SeasonCreateParams{
    Name: "2027 subscription",
    SourcePerformanceGroupKeys: []string{"pg_subscription_run"},
    IdempotencyKey: "season-create-2027",
})

Treat 202 as accepted work and poll RetrieveLifecycle with the returned operation identity. Buyer-session minting and domain-exact booking, cancellation, and renewal actions remain single-attempt; only declared header-replay catalogue mutations retry automatically.

When SeatLayer takes the Season's payments, renew a holder with no hosted page: read the price, start a checkout, and send the holder to payment.url. For a renewal taken by phone or at the box office, CreateRenewalIntent records the holder's intent before you commit the offer. A decline can carry the holder's reason.

quote, err := client.Seasons.RetrieveRenewalQuote(ctx, seasonKey, offerID)
if err != nil {
    return err
}
if quote["problem"] == nil {
    started, err := client.Seasons.CreateRenewalCheckout(ctx, seasonKey, offerID,
        seatlayer.SeasonRenewalCheckoutParams{
            ReturnURL:      "https://tickets.yourclub.example/renewed",
            IdempotencyKey: "renew-" + offerID,
        })
    if err != nil {
        return err
    }
    redirect(started["payment"].(map[string]any)["url"].(string))
}

// For one holder's offer:
_, err = client.Holders.DeclineRenewalOffer(ctx, "holder_123", offerID,
    &seatlayer.SeasonRenewalDeclineParams{Reason: "Moving away"})

Season seats

Season seats are the seats a season sets aside on every date before its sales open. A season buyer's hold then doesn't have to ask each date, and single-ticket buyers see those seats as unavailable. They are off for every season until you set InventoryMode to seatlayer.SeasonInventoryModeBlock on Create or Update. The season needs one package, and its sales must not have opened. Turning them on keeps the season's revision.

_, err := client.Seasons.Update(ctx, seasonKey, seatlayer.SeasonUpdateParams{
    ExpectedRevision: revision, InventoryMode: seatlayer.SeasonInventoryModeBlock,
})
answer, err := client.Seasons.SetAsideBlock(ctx, seasonKey, seatlayer.SeasonBlockSetAsideParams{
    ExpectedRevision:     revision,
    KeepForSingleTickets: &seatlayer.SeasonSeatSelection{SectionIDs: []string{"sec_family"}},
})
job := answer["job"].(map[string]any)
for job["state"] == "planning" || job["state"] == "running" {
    time.Sleep(2 * time.Second)
    read, err := client.Seasons.RetrieveBulkJob(ctx, seasonKey, job["jobId"].(string))
    if err != nil {
        return err
    }
    job = read["job"].(map[string]any)
}
block, err := client.Seasons.RetrieveBlock(ctx, seasonKey)
rev := int(block["block"].(map[string]any)["revision"].(float64))

// Release unsold season seats to single sale. Leave At empty to release now.
_, err = client.Seasons.ReleaseBlock(ctx, seasonKey, seatlayer.SeasonBlockReleaseParams{
    ExpectedRevision: rev,
    Scope:            seatlayer.SeasonBlockReleaseScope{All: true},
    At:               "2026-12-01T09:00:00Z",
})
  • On an active season, block mode turns on by itself once every date has the seats, and WebhookEventSeasonBlockReady (season.block.ready) is sent once. On a draft, Publish turns it on. WebhookEventSeasonBlockReleased (season.block.released) is sent once a release has run.
  • In block mode, a date cancel (CreateAmendment, kind cancel) or MarkPerformancePast first takes the date off every season seat. Until that finishes the answer is a 202: check it with seatlayer.IsSeasonDateLeavePending(answer), then send the same request again after Retry-After.
  • Refusals come through *APIError and its typed wrappers with Code, and nothing changes. For example 409 season_block_packages (more than one package), 409 season_block_on_sale (sales have opened), or 409 block_not_ready from OpenSales before block mode is on.
  • A partner's seat on a date (one a sales channel sells there) stays out of the block. It is listed in the block's notFreeOnEveryDate with reason channel.

The Season seats docs list every refusal.

Test vs live

Keys carry their own mode. sk_test_… keys can only touch test-mode events and sk_live_… only live ones; crossing them returns 403 mode_mismatch.

client, err := seatlayer.New(os.Getenv("SEATLAYER_SECRET_KEY"))
if err != nil {
    return err
}
if os.Getenv("ENV") == "production" && client.Mode() != "live" {
    return errors.New("refusing to boot production against test-mode seating data")
}

A publishable pk_ key is rejected by New with a message naming the mistake, rather than failing as a 401 three round-trips later.

Book reserved seats from Go

Buyer picks seats in the browser. Your frontend holds them; your backend confirms the price and books. Never price from what the browser sent you: RetrieveHold is authoritative.

import "errors"

hold, err := client.Inventory.RetrieveHold(ctx, eventKey, holdID)
if err != nil {
    return err
}
if len(hold.Items) == 0 {
    return errors.New("hold has no items")
}
currency := hold.Items[0].Currency
total := 0.0
for _, item := range hold.Items {
    if item.Currency != currency {
        return errors.New("a hold must use one currency")
    }
    quantity := 1
    if item.Quantity != nil {
        quantity = *item.Quantity
    }
    total += item.UnitPrice * float64(quantity)
}
// … charge `total` in `currency` …
_, err = client.Inventory.Book(ctx, eventKey, seatlayer.BookParams{
    HoldID: holdID, BookingRef: charge.ID,
})

Your backend picks the seats. Phone orders, box office, comps.

// Payment already taken: book outright, so nothing is stranded if a second call fails.
_, err := client.Inventory.BookBestAvailable(ctx, eventKey, seatlayer.BestAvailableParams{
    Qty: 2, BookingRef: "phone-1183",
})

// Or name the seats yourself.
_, err = client.Inventory.BoxOfficeBook(ctx, eventKey, []string{"A-1", "A-2"}, "comp-14")

Private and partner sales

Channels reserve inventory for a partner, member group, presale, or other private allocation. A buyer access session is short-lived and origin-bound, so the browser receives only the allocation it is allowed to sell; your secret key remains on your server.

_, err := client.Channels.CreateChannel(ctx, eventKey, seatlayer.ChannelCreateParams{
	Name:         "Venue members",
	AccessIntent: "private",
})

_, err = client.Channels.UpdateAssignments(ctx, eventKey, seatlayer.ChannelAssignmentParams{
	Labels:            []string{"A-1", "A-2"},
	AssignmentVersion: 1,
	TargetChannelID:   "ch_members",
})

access, err := client.Channels.CreateBuyerAccessSession(ctx, eventKey,
	seatlayer.BuyerAccessSessionParams{
		ChannelIDs:    []string{"ch_members"},
		IncludePublic: false,
		AllowedOrigin: "https://members.example",
		MaxQuantity:   2,
	})

Pass the returned token to the buyer SDK. Trusted backend sale params accept ChannelIDs, an explicit privileged IgnoreChannelRestrictions flag, and an audit Reason.

Listing and pagination

List returns one Page plus a cursor. All is a range-over-func iterator that pages as you consume it. It is deliberately not a slice, because the point of paginating is to not hold an unbounded result set in memory.

// One page, your own paging.
page, err := client.Events.List(ctx, &seatlayer.EventListParams{Limit: 50})
page.Items
page.NextCursor   // "" once exhausted

// Or let the SDK walk it.
for event, err := range client.Events.All(ctx, nil) {
    if err != nil {
        return err
    }
    sync(event)
}

The error rides alongside each item so a failed page reaches you. An iterator that silently ended on error would look identical to a list that finished.

Listing events includes live availability counts by default, which costs the server one round-trip per event. All drops them automatically, since walking a whole catalogue is exactly when you don't want that, and you can control it explicitly:

client.Events.List(ctx, &seatlayer.EventListParams{Limit: 50, NoCounts: true})

Set SeriesKey to list only the dates of one event series.

Keeping a hold alive

When an order takes longer than the checkout window (an invoice, a phone sale), extend rather than release and re-hold. Releasing first hands the seats to whoever is racing for them in between.

_, err := client.Inventory.ExtendHold(ctx, eventKey, holdID, 10*60*1000)

var conflict *seatlayer.ConflictError
if errors.As(err, &conflict) {
    // Gone, expired, or at its renewal cap: the buyer has to re-pick.
}

Embedding the control room

Your secret key never reaches a browser. Mint a scoped token instead.

session, err := client.Sessions.CreateManageSession(ctx, eventKey, seatlayer.ManageSessionParams{
    AllowedOrigin:    "https://box-office.yourplatform.com",
    Capabilities:     []seatlayer.ManageCapability{
        seatlayer.CapabilityView,
        seatlayer.CapabilityBlock,
    },
    ExpiresInSeconds: 3600,
})

Capabilities is required by this SDK even though the raw API safely defaults an omitted list to view-only (event:view). Keeping the field required makes browser authority visible at every call site. Grant the smallest set the page needs. For Platform/SDK events, event:cancel returns a booking's inventory to sale but does not move gateway money. The Managed commerce constants cover eligible Managed Ticketing orders, refunds, ticket delivery, door, and box-office capabilities.

Designer minting returns a DesignerSessionEnvelope; the token and the effective safe-mode and feature policy live under result.Session. Pass SafeModeOptions only with Mode: "safe".

Webhooks

Webhook methods expose the wire envelopes directly: List returns WebhookList.Subs, Create returns WebhookCreateEnvelope with the show-once Secret, and Update returns WebhookEnvelope.Sub. Use the WebhookEvent… constants for the 56 accepted event names and WebhookDeliveryListParams for limit, status, and before filters.

Verify every delivery against the raw body. Decoding and re-encoding changes the bytes. In Go specifically, encoding/json marshals map keys in sorted order while a real delivery arrives in the order we serialised it, so a round trip reorders it and verification fails.

func handleWebhook(w http.ResponseWriter, r *http.Request) {
    payload, err := io.ReadAll(r.Body)   // raw bytes, before any decoding
    if err != nil {
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    event, err := seatlayer.VerifyWebhook(
        payload,
        r.Header.Get("X-SeatLayer-Signature"),
        os.Getenv("SEATLAYER_WEBHOOK_SECRET"),
    )
    if errors.Is(err, seatlayer.ErrWebhookVerification) {
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    // The signed body carries "at", but nothing enforces a freshness window, so a
    // captured delivery stays valid indefinitely. Deduplicate on occurrenceId:
    // this is your replay protection, not an optimisation.
    if alreadyProcessed(event["occurrenceId"].(string)) {
        w.WriteHeader(http.StatusOK)
        return
    }

    process(event)
    w.WriteHeader(http.StatusOK)
}

Errors

Errors are values here, not exceptions, so reach for errors.As:

_, err := client.Inventory.HoldBestAvailable(ctx, eventKey, seatlayer.BestAvailableParams{Qty: 6})

var conflict *seatlayer.ConflictError
var rateLimit *seatlayer.RateLimitError
var auth *seatlayer.AuthError

switch {
case errors.As(err, &conflict) && conflict.SoldOut():
    return offerAlternativeDates()          // a business outcome, not a bug
case errors.As(err, &rateLimit):
    return retryAfter(rateLimit.RetryAfter)
case errors.As(err, &auth) && auth.ModeMismatch():
    return errors.New("test key pointed at a live event, or the reverse")
case err != nil:
    return err
}
Type Status Means
AuthError 401, 403 Bad, revoked, or wrong-mode key
NotFoundError 404 No such resource for this organisation
ConflictError 409 Inventory moved, or a guard rejected the change
ValidationError 422 Understood and rejected
RateLimitError 429 Over budget; carries RetryAfter
ConnectionError none No answer: DNS, TLS, socket, context deadline (unwraps)

Every API error carries Status, Code, Body, and RequestID. Quote the request id in support requests.

Reliability

Retries. Reads (GET/HEAD) retry 429, 408 and 5xx with exponential backoff and full jitter; Retry-After wins when the server sends it. Forty-one mutations use exact header replay: Charts.Create, Charts.Copy, Templates.InstantiateTemplate, Events.Create, Workspaces.Create, PerformanceGroups.Create, Seasons.Create, Seasons.Update, Seasons.Delete, Seasons.CreatePlan, Seasons.UpdatePlan, Seasons.DeletePlan, Seasons.Publish, Seasons.DuplicateToLive, Seasons.CreateHolderImport, Seasons.CreateHolderImportFromSeason, Seasons.CreateRenewalOffers, Seasons.CreateAmendment, Seasons.CreatePerformances, Seasons.AttachPerformances, Seasons.DeletePerformance, Seasons.MarkPerformancePast, Seasons.CreateCheckoutTest, Seasons.ReplacePackagePrices, Seasons.AttachPlanPerformance, Seasons.DeletePlanPerformance, Seasons.RefundOrder, Seasons.CancelOrder, Seasons.CancelOrderDate, Seasons.RefundPerformance, Seasons.ReleaseLapsedOffers, Seasons.CreateRenewalCheckout, Holders.Update, Holders.CreateTransfer, Holders.CreateReturn, Holders.CreateRenewalCheckout, Orders.RefundEventOrder, Orders.CancelEventOrder, EventSeries.Create, EventSeries.AddDates, and EventSeries.Update. Other 4xx responses are never retried.

Idempotency. Those 41 replay-backed operations carry an Idempotency-Key, generated when you do not supply one and reused across attempts. All remaining SDK mutations are single-attempt. Some have a server-side domain idempotency contract, but the SDK does not retry them automatically. This includes inventory holds and bookings, show-once credential or secret creation, unsupported operations, and raw Do mutations. Keep BookingRef in the booking body for reconciliation, but handle an unknown network outcome explicitly instead of automatically repeating the sale.

client.Events.Create(ctx, seatlayer.EventCreateParams{
    ChartID: chartID, IdempotencyKey: "provision-event-" + eventID,
})
client, err := seatlayer.New(
    os.Getenv("SEATLAYER_SECRET_KEY"),
    seatlayer.WithMaxRetries(3),
    seatlayer.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
)

Client is safe for concurrent use.

Escape hatch

For surface this SDK does not wrap yet, Do keeps auth and error mapping. Raw reads retain the read retry policy; raw mutations are always single-attempt because their replay contract is unknown:

client.Do(ctx, http.MethodPost, "/v1/events/ev_1/some-new-route", nil, map[string]any{"qty": 2}, "")

API surface

The client exposes these services. Performance Groups cover runs, sessions, holds, and bookings; Seasons cover catalogue, plan, sales, buyer-session, booking, renewal, occurrence, reporting, outbox, and support operations. Event sales settings (checkout choice, payment gateway, sales readiness and transitions, booking fee, discount codes) live on Events. The three CSV exports return the CSV text as a string.

Service Methods
Charts List All Create Retrieve Update Delete Copy Archive Unarchive Publish
Templates InstantiateTemplate
Events List All Create Retrieve RetrieveConfigurationBinding UpdateConfigurationBinding Update Delete UpdatePoster DeletePoster UpdateChart Close Reopen Archive ListTicketReleases UpdateTicketReleases CloseTicketRelease RetrieveHoldTTL UpdateHoldTTL RetrieveReport RetrieveLog RetrieveBuyerCheckout ReplaceBuyerCheckout RetrievePaymentGateway ReplacePaymentGateway RetrieveSalesReadiness PreflightSales TransitionSales RetrieveBookingFee ReplaceBookingFee ListDiscountCodes CreateDiscountCode UpdateDiscountCode EndDiscountCode DeleteDiscountCode
EventSeries Create List Retrieve AddDates Update
Orders List Retrieve ListTickets ListEventOrders RetrieveEventOrder ExportEventOrdersCSV RefundEventOrder CancelEventOrder ResendEventOrderTickets
Holders ListCustomers Retrieve Update RevokeSessions ListSeasons ListTickets RetrieveTicket CreateTransfer ListTransfers CancelTransfer AcceptTransfer CreateReturn CreateRenewalCheckout DeclineRenewalOffer
Channels ListChannels CreateChannel UpdateChannel UpdateAssignments ListAllocation RetrieveAccessPreview RetrieveReport Pause Unpause Archive CreateBuyerAccessSession ListBuyerAccessSessions RevokeBuyerAccessSession CreateAccessLink ListAccessLinks RotateAccessLink RevokeAccessLink
Inventory Hold HoldBestAvailable BookBestAvailable ExtendHold RetrieveHold Release Book BoxOfficeBook Unbook Block Unblock UnblockAll RetrieveAvailability UpdateAvailability ListBookings RetrieveBooking
Sessions CreateManageSession RevokeManageSession CreateDesignerSession RevokeDesignerSession
Webhooks List Create Update Delete ListDeliveries
Workspaces List Create Retrieve Update
PerformanceGroups List Create Retrieve Delete Activate Close RetrieveLifecycle CreateBuyerAccessSession ListBuyerAccessSessions RevokeBuyerAccessSession RetrieveHold BookHold RetrieveBooking
Seasons 87 operations for catalogue and Plan lifecycle, performances, package prices, publishing, checkout tests, sales windows, buyer access and booking, holder imports and the holder list, Season orders, renewals, hosted renewals and renewal checkouts, occurrence amendments and inventory returns, bulk refunds, lapsed-offer releases and their jobs, reports and CSV exports, audit, outbox, and support export

Full reference: SeatLayer Go server SDK guide

Frequently asked questions

How do I book seats from Go?

Add the github.com/seatlayer/seatlayer-go module, construct a client with seatlayer.New and your secret key, and call client.Inventory.Book with the hold id and a stable BookingRef. When your own backend picks the seats (phone orders, box office, comps), Inventory.BookBestAvailable and Inventory.BoxOfficeBook book outright with no prior hold. A booking reference is required on every booking call, so each sale is tied to an immutable order id you can reconcile against later.

What does the server SDK do that the buyer SDK does not?

The buyer SDK runs in the browser or mobile app and only selects and holds seats. This Go SDK runs on your trusted server and inspects and books them. Your secret key never reaches a buyer surface: browsers receive short-lived, origin-bound tokens minted here through Sessions.CreateManageSession or Channels.CreateBuyerAccessSession. Always price a sale from Inventory.RetrieveHold, never from values the browser sent you.

How do temporary seat holds work server-side?

A hold reserves seats against concurrent buyers for a limited checkout window. From Go you retrieve it with Inventory.RetrieveHold, whose item-level price, quantity, and currency are authoritative, and confirm it with Inventory.Book. Use Inventory.ExtendHold for a long checkout instead of releasing and re-holding, which would hand the seats to whoever is racing for them. Booking is a single automatic attempt: after an unknown network outcome you may reconcile and repeat the exact same event, hold, and BookingRef; seats already booked under that reference are not sold again.

Can I use my own payment provider?

Yes. This server SDK does not process payment in a Platform/SDK integration. Charge through the provider you already use, calculating the total from each server-inspected hold item's UnitPrice, Quantity, and Currency, then call Inventory.Book with your charge or order id as the BookingRef. Managed Ticketing is a separate product path with organizer-connected payments. The holds and checkout guide walks through the full handoff.

Continue your Go integration

SeatLayer SDK ecosystem

Surface Package or source
JavaScript @seatlayer/js
React @seatlayer/react
React Native @seatlayer/react-native
iOS seatlayer-ios
Flutter seatlayer
Android seatlayer-android
Server SDKs Node.js, Python, PHP, Ruby, .NET, Java, and Go
Node.js (server) @seatlayer/server
Python (server) seatlayer
PHP (server) seatlayer/seatlayer-php
Ruby (server) seatlayer
.NET (server) SeatLayer
Java (server) io.seatlayer:seatlayer-java
Go (server) github.com/seatlayer/seatlayer-go (this module)

Development

gofmt -l .          # must be empty
go vet ./...
go test -race ./...

License

MIT

About

Official Go server SDK for SeatLayer reserved seating: seating charts, seat maps, events, holds, booking and webhook verification, with idempotency and retries built in.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages