StrikerLeagues for developers

Put your field bookings anywhere.

A booking widget for your website, a read-only API for your own systems, webhooks the moment a booking changes, and calendar feeds. Set it all up from your venue’s Integrations tab.

Overview

Everything here belongs to one venue. Its owner (or StrikerLeagues support) turns things on in My venues → your venue → Integrations:

  • Booking widget

    Free times on your own website. No coding: paste one snippet.

  • Read-only API

    Your fields, free times, bookings and blocks, as JSON, with an API key.

  • Webhooks

    A signed POST to your server when a booking is confirmed, requested, declined or cancelled.

  • Calendar feeds

    Bookings in Google, Outlook or Apple Calendar, and busy times from your calendars blocking bookings.

Booking and paying always happen on StrikerLeagues, signed in, so card details never touch your site. The API reads; it doesn’t create or change bookings.

Booking widget

Copy your venue’s snippet from Integrations → Booking widget, or write it yourself with your venue’s page name (the part after /fields/ in its booking link):

HTML
<div data-strikerleagues-venue="zilker-park-soccer-fields-austin"></div>
<script src="https://www.strikerleagues.com/widget.js" async></script>

The widget shows the next two weeks of free times and resizes itself to fit. Choosing a time opens your StrikerLeagues page in a new tab with that time picked, where the player signs in and pays. Options go on the div:

ParameterTypeWhat it does
data-theme"light" | "dark"Colours to match your site. Light by default.
data-fieldfield idShow one field only (ids are in the API, or pick it in the Integrations tab).
data-days7 – 14How many days ahead to offer. 14 by default.
data-mode"button"Just a “Book now” button that opens your booking page.
data-labeltextThe button’s text, in button mode.

Site builders that don’t allow scripts can use a plain iframe instead (set the height yourself):

HTML
<iframe src="https://www.strikerleagues.com/embed/fields/zilker-park-soccer-fields-austin?theme=dark"
  title="Book on StrikerLeagues" loading="lazy"
  style="width:100%;max-width:640px;height:560px;border:0;border-radius:16px"></iframe>

API keys

The venue’s owner makes keys in Integrations → API & webhooks. A key starts with sl_live_, is shown once, and reads that venue only. Send it in the Authorization header (or X-Api-Key):

curl https://strikerledger-production.up.railway.app/api/v1/open/bookings \
  -H "Authorization: Bearer sl_live_…"

Keys read bookings and bookers’ names. Use them from your server only, never in a web page or an app people can download. Revoke a key in the Integrations tab if it leaks.

All times are UTC (ISO 8601). Money is in the smallest unit (cents) with its currency. The venue’s time zone is in /venue.

Errors and limits

Every response has the same shape. Errors use the HTTP status plus a code you can check:

JSON
{
  "success": false,
  "error": { "code": "API_KEY_INVALID", "message": "That API key isn’t valid (it may have been revoked)." },
  "meta": { "timestamp": "2026-11-06T15:04:53.093Z" }
}
ParameterTypeWhat it does
401API_KEY_MISSING / API_KEY_INVALIDNo key, or a revoked or unknown one.
400VALIDATION_ERRORA query parameter is wrong; the message says which.
404NOT_FOUNDNo such endpoint, or the booking isn’t at your venue.
429RATE_LIMITEDMore than 120 requests in a minute with one key. Wait Retry-After seconds.

Each response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds until the minute resets). To keep in sync, prefer webhooks over polling, or poll /bookings?updatedSince=… every few minutes.

Endpoints

Base URL: https://strikerledger-production.up.railway.app/api/v1/open

GET/venue

The venue: name, address, location, time zone, contact details, cancellation policy and its public booking link (bookingUrl).

GET/fields

Every field: surface, indoor, lights, game sizes (5v5 … 11v11), bookingMode (instant or request), weekly opening hours (local time, dayOfWeek 0 = Sunday) and hourly prices.

GET/availability

Free, bookable times, exactly as players see them on your page (opening hours, minus bookings, blocks and busy times from imported calendars).

ParameterTypeWhat it does
dateYYYY-MM-DDRequired. The venue’s local date.
days1 – 14How many days from date. Default 1.
durationMinutes30 – 240Length of each slot. Default 60.
gameFormat5v5 … 11v11Only fields that take this game size.
fieldIduuidOne field only.
Response
{
  "success": true,
  "data": {
    "timeZone": "America/Chicago",
    "durationMinutes": 60,
    "bookingUrl": "https://www.strikerleagues.com/fields/zilker-park-soccer-fields-austin",
    "fields": [
      {
        "id": "1b3bd028-…",
        "name": "Field 1",
        "gameFormats": ["7v7", "11v11"],
        "slots": [
          { "startsAt": "2026-11-16T23:00:00.000Z", "endsAt": "2026-11-17T00:00:00.000Z", "price": { "amount": 9500, "currency": "usd" } }
        ]
      }
    ]
  }
}

To send someone straight to a time, link to bookingUrl with ?date=2026-11-16&format=SEVEN_V_SEVEN&minutes=60&field=<id>&start=<startsAt>#book.

GET/bookings

Bookings in start-time order, 50 a page (up to 100). Holds and unfinished checkouts are left out. Page with cursor: pass the meta.nextCursor of the previous page until it’s null.

ParameterTypeWhat it does
fromISO date-timeBookings starting at or after this. Default: now.
toISO date-timeBookings starting before this.
statussee belowconfirmed, requested, declined, cancelled, completed or no_show.
fieldIduuidOne field only.
updatedSinceISO date-timeOnly bookings changed since then (handy for polling).
limit1 – 100Page size. Default 50.
cursorstringFrom meta.nextCursor.
One booking
{
  "id": "4034ef5c-5693-402a-ba1b-696611958ec1",
  "status": "confirmed",
  "field": { "id": "1b3bd028-4027-4e9b-9d4c-3da97d4e31d7", "name": "Field 1" },
  "startsAt": "2026-11-18T00:00:00.000Z",
  "endsAt": "2026-11-18T01:00:00.000Z",
  "gameFormat": "7v7",
  "price": { "amount": 12000, "currency": "usd" },
  "booker": { "name": "Pat Player" },
  "series": null,
  "leagueMatchId": null,
  "cancelledAt": null,
  "cancellationReason": null,
  "createdAt": "2026-11-06T15:05:56.000Z",
  "updatedAt": "2026-11-06T15:05:56.000Z"
}

status: requested is waiting for the venue’s answer (card authorised, not charged); declined was turned down or not answered in 24 hours; cancelled covers cancellations by the player or the venue and refunds. series is set for weekly bookings ({ id, week }); leagueMatchId when a league booked it for a match.

GET/bookings/{id}

One booking, in the shape above. 404 if it isn’t at your venue.

GET/blocks

Time the venue blocked: kind is internal (your own club or league use), closure, or imported_calendar (busy in a calendar you imported). from defaults to now and to to 31 days later (at most 366 days); fieldId filters.

Webhooks

Add up to five https endpoints in Integrations → API & webhooks, choose their events, and use Send test to try one. We POST JSON like this:

POST your-endpoint
StrikerLeagues-Event: booking.confirmed
StrikerLeagues-Delivery: 9d0c3f9e-…
StrikerLeagues-Signature: t=1793977556,v1=5f1c0b…
Content-Type: application/json

{
  "id": "evt_9035cf198177d85cfd34b50f",
  "type": "booking.confirmed",
  "created": "2026-11-06T15:05:56.681Z",
  "venueId": "2781d6fe-ac44-4c18-9a39-f1b86f83e1b5",
  "data": { "booking": { …the booking, as in the API… } }
}

Events

ParameterTypeWhat it does
booking.confirmedeventA booking is paid and confirmed, or the venue approved a request.
booking.requestedeventSomeone asked to book a request-to-book field. Answer within 24 hours on StrikerLeagues.
booking.declinedeventA request was declined, or nobody answered in time.
booking.cancelledeventCancelled by the player or the venue, or a weekly payment failed.
webhook.testeventSent by the Send test button (data.message only).

A weekly booking sends one event per week’s booking. Events can arrive out of order, so compare updatedAt if order matters.

Checking signatures

Each endpoint has a signing secret (whsec_…, shown in the Integrations tab). StrikerLeagues-Signature holds a Unix timestamp t and v1, the hex HMAC-SHA256 of "<t>.<raw body>" with your secret. Check it against the raw body, reject old timestamps, and compare in constant time:

import crypto from 'node:crypto';
import express from 'express';

const app = express();
const SECRET = process.env.STRIKERLEAGUES_WEBHOOK_SECRET; // whsec_…

// Use the raw body: re-serialised JSON won't match the signature.
app.post('/strikerleagues/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('StrikerLeagues-Signature') || '';
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  const expected = crypto.createHmac('sha256', SECRET).update(`${t}.${req.body}`).digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - t) < 300; // 5 minutes
  const valid =
    fresh && parts.v1?.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
  if (!valid) return res.sendStatus(400);

  const event = JSON.parse(req.body);
  // Events can arrive more than once: skip ids you've already handled.
  if (event.type === 'booking.confirmed') {
    // event.data.booking …
  }
  res.sendStatus(200); // answer fast; do slow work afterwards
});

Retries

  • Answer with any 2xx within 10 seconds. Redirects aren’t followed.
  • Otherwise we try again after 1 minute, 5 minutes, 30 minutes, 2, 6, 12 and 24 hours: eight tries in all.
  • The same event can arrive more than once. Use its id to skip repeats.
  • After 50 failed tries in a row an endpoint is switched off. Fix it, then switch it back on in the Integrations tab.
  • The last 25 deliveries per endpoint, with their payloads, are listed under Recent deliveries (kept for 30 days).

Calendar feeds

Integrations → Calendar sync gives each venue a private iCal link (all fields, or one per field) of bookings, requests and blocks. Calendar apps check it every 30 minutes or so. Anyone with the link can read it, so it can be reset there.

The same page imports other calendars (Google, Outlook, iCloud, another booking system) by their iCal link: their events block the fields you choose, re-checked every 30 minutes, 180 days ahead. Imported busy times show in /blocks as imported_calendar.

Help

Stuck, or need something the API doesn’t do yet? Use Help in the app or contact support. By using the API you agree to our Terms, including keeping players’ details private.