View Categories

REST API: locations, the map and the messaging webhook

12 min read

The REST API for product locations and the map: the marker data every map is drawn from, a distance search over your rentals, and the map’s own query with its result cards. This page also covers the messaging webhook, the address an SMS or WhatsApp provider calls with delivery receipts and customer replies. The setup and the conventions shared by every endpoint (keys, errors, pagination) are in REST API overview and authentication.

Base URLs and sign-in #

NamespaceWhat lives thereNeeds
/wp-json/wc-sibooking/v1/locations: product markers, availability for dates, the distance searchRentals & Bookings Regular 1.0.74 or newer
/wp-json/sibooking/v1/map: the map’s query with its result cardsRentals & Bookings Regular 1.0.74 or newer
/wp-json/sibooking/v1/messaging/webhook/{provider}: the provider’s callbackRentals & Bookings Regular 1.0.66 or newer
  • wc-sibooking/v1 takes a WooCommerce REST API key (WooCommerce > Settings > Advanced > REST API) sent as HTTP Basic, like the other rental endpoints. An Application Password works there too.
  • sibooking/v1/map does not start with wc-, so WooCommerce keys don’t apply there: a key sent as HTTP Basic answers 401. Use a WordPress Application Password (Users > Profile > Application Passwords) as HTTP Basic (username:application password), or, inside wp-admin, the logged-in cookie with an X-WP-Nonce header.
  • The messaging webhook takes no login. The provider’s signature on each request is the credential (see below).
  • locations and map have been in Rentals & Bookings since 1.0.74, the messaging webhook since 1.0.66.
  • Errors use the WordPress shape {"code", "message", "data": {"status"}}. The locations codes start with sibooking_rest_, the map codes with sibooking_map_. The webhook answers with its own short JSON, listed with it.
STORE="https://example.com/wp-json"
curl -u "ck_your_key:cs_your_secret" "$STORE/wc-sibooking/v1/locations?category=kayaks"
curl -u "username:YOUR_APP_PASSWORD" "$STORE/sibooking/v1/map?bounds=44.85,-93.40,45.00,-93.20"

Who may do what #

AbilityWho has itOpens
manage_woocommerceShop managers and administratorsGET /wc-sibooking/v1/locations and GET /sibooking/v1/map
No loginYour SMS or WhatsApp providerThe messaging webhook. Every request must carry the provider’s valid signature

A user without manage_woocommerce gets 401 (not signed in) or 403: code sibooking_rest_forbidden on locations, rest_forbidden on map.

Product locations #

A product goes on a map when it is published, visible in the catalogue (not “Hidden” or “Search results only”), not password protected, and has coordinates on its Location tab. The maps themselves are described in Maps.

GET /wc-sibooking/v1/locations #

Lists the products as map markers, optionally with availability for dates and narrowed to a distance from a point.

ParameterNotes
categoryProduct category slugs, comma separated. Child categories are included. Slugs that match no category are skipped; when none match, the list is empty
idsProduct ids, comma separated. A variation’s id means its parent product
limitInteger, 1 to 500. Left out: up to 500 (filter sibooking_map_max_markers). The limit is applied before the distance search, so with both, only the first limit products are measured
from, toY-m-d. Send both. Each rental then carries available and total for those dates. The dates are checked for format and order only; past dates are not refused
qtyInteger, at least 1, default 1. The units the availability and the total are for
lat, lngDecimal degrees (-90 to 90, -180 to 180): the center of a distance search
radiusNumber above 0, in unit. Send it with lat and lng
unitkm (default) or mi

Without a distance search the markers come in menu order, then by name. With one they come nearest first, and each has distance (in unit, two decimals) and unit. The list is not paged: X-WP-Total is the number of markers and X-WP-TotalPages is always 1.

Each marker:

FieldNotes
id, name, urlThe product id, its name as plain text, its permalink
imageThe catalogue thumbnail, or WooCommerce’s placeholder
imagesUp to five {src, srcset, alt}: the product image, then the gallery (filter sibooking_map_card_image_count, 1 to 10)
priceOne plain-text line: From $45.00 / day for a rental, the product’s own price otherwise
lat, lngSix-decimal strings: the public point (see Approximate locations below)
addressThe address on one line; for an approximate location, the area only (Minneapolis, MN)
location_nameThe location’s name from the Location tab
locationThe whole public location: name, address_1, address_2, city, state, postcode, country (two-letter code), lat, lng, show_map, formatted, description, privacy (exact or approximate), approximate, radius
areaThe location’s name and its town: Lake Nokomis, Minneapolis
rentaltrue for a rental product
availableWith dates: true or false for a rental, judged as the shop grid judges the same dates. null without dates, and for a product that is not a rental
unavailable_labelThe shop’s “Not available for your dates” wording (Rentals > Settings > Search) when available is false, else empty
approximate, radiusWhether the location is approximate, and its circle in meters (0 when exact)
rating{average, count}, or null when ratings are off or there are no reviews
factsOne short line from the sibooking_map_card_facts filter; empty by default
fromThe lowest rate, {amount, text, template, period}, or null. template has %s where the price goes: From %s / day
totalWith dates, the price for those dates, as the cart would charge it, times qty: {amount, text, per_amount, per_text, per_template, count, period, label, quantity, total_template}. null without dates, for a product that is not a rental, or when the rental is not priced for that length (a monthly rental in a two-night search shows its from instead)
pin, pin_labelThe short price for a price pin ($45: the total when there is one, else the from amount) and the same in words
distance, unitOnly on a distance search

Approximate locations. A product set to Approximate (on its Location tab, or by the store default in Rentals > Settings > Maps) never gives out its real place. Its lat and lng are a fixed point inside its circle, not the real one (the same point on every request), address_1, address_2 and postcode are empty, formatted and address are the area only, and approximate is true. A distance search measures from that public point too, so repeated searches cannot narrow down the real address. The real address is shown only on the customer’s order after booking.

ErrorWhen
400 sibooking_rest_invalid_datesfrom or to is missing or not Y-m-d, or to is before from
400 sibooking_rest_invalid_pointA distance search without all three of lat, lng and a radius above 0
400 rest_invalid_paramA value out of range: lat, lng, a negative radius, unit other than km or mi, limit outside 1 to 500, qty below 1
401 / 403 sibooking_rest_forbiddenNot signed in, or the user lacks manage_woocommerce

Kayaks in one category, with availability for two units over a weekend:

curl -u "ck_your_key:cs_your_secret" \
  "$STORE/wc-sibooking/v1/locations?category=kayaks&from=2026-12-04&to=2026-12-06&qty=2"

Everything within 5 miles of a point, nearest first:

curl -u "ck_your_key:cs_your_secret" \
  "$STORE/wc-sibooking/v1/locations?lat=44.9778&lng=-93.2650&radius=5&unit=mi"
[
  {
    "id": 456,
    "name": "Lakeside cabin",
    "url": "https://example.com/product/lakeside-cabin/",
    "image": "https://example.com/wp-content/uploads/lakeside-cabin-300x300.jpg",
    "price": "From $180.00 / day",
    "lat": "44.920700",
    "lng": "-93.301900",
    "address": "Minneapolis, MN",
    "location_name": "Lake Harriet",
    "location": {
      "name": "Lake Harriet", "address_1": "", "address_2": "",
      "city": "Minneapolis", "state": "MN", "postcode": "", "country": "US",
      "lat": "44.920700", "lng": "-93.301900", "show_map": true,
      "formatted": "Minneapolis, MN", "description": "",
      "privacy": "approximate", "approximate": true, "radius": 500
    },
    "rental": true,
    "available": null,
    "unavailable_label": "",
    "approximate": true,
    "radius": 500,
    "area": "Lake Harriet, Minneapolis",
    "images": [{"src": "https://example.com/wp-content/uploads/lakeside-cabin-600x600.jpg", "srcset": "...", "alt": "Cabin by the lake"}],
    "rating": null,
    "facts": "",
    "from": {"amount": 180, "text": "$180.00", "template": "From %s / day", "period": "day"},
    "total": null,
    "pin": "$180",
    "pin_label": "From $180.00 / day",
    "distance": 4.34,
    "unit": "mi"
  },
  {"id": 123, "name": "Two-person kayak", "lat": "44.909700", "lng": "-93.241000", "approximate": false, "radius": 0, "distance": 4.85, "unit": "mi", "...": "..."}
]

With dates, a rental’s total looks like this:

"total": {"amount": 360, "text": "$360.00", "per_amount": 180, "per_text": "$180",
  "per_template": "%s / day", "count": 2, "period": "day", "label": "for 2 days",
  "quantity": 1, "total_template": "%s total"}

The map query #

GET /sibooking/v1/map #

The map’s own query: the markers and the result cards for one part of the map, the same answer a split-layout [sibooking_map] gets when a visitor moves the map. It returns only what the shortcode would print on a public page. The storefront map does not call this route (it asks through admin-ajax and gets the same answer), so use it from your own code with a staff login.

ParameterNotes
boundssouth,west,north,east in decimal degrees. Only markers whose public point is inside. A west value greater than the east value is a box across the 180th meridian
category, ids, limitAs on locations; limit is 1 to 500
search1 (default) or 0. With 0, the dates, len and the guests are ignored
from, toY-m-d. Checked for format and order only; past dates are not refused
from_time, to_timeH:i, for rentals by the hour
lenA length from from, instead of to: a number and h, d, w, m or y (4h, 3d, 2w, 1m). At most 720 hours, 730 days, 104 weeks, 24 months or 2 years. A length in hours without from_time starts at the store’s opening time
qtyInteger, at least 1, default 1
periodEmpty (default: each product’s own wording), day or night: words the prices per day or per night
pinsprice (default) or marker. Accepted; it does not change the answer
guests0 to 99: the party size, adults plus children. Rentals that can never hold the party are left out, and the dates are judged for the party
children, infants, pets0 to 99. Read only when guests is sent

When the shop’s “Filter products by the visitor’s dates” setting (Rentals > Settings > Search) is set to hide unavailable products, rentals that are not available for the dates are left out of the answer. Otherwise they come back with available: false.

FieldNotes
countThe number of markers
count_label2 rentals, in the store’s language
region_labelMap of 2 rental locations: the map’s label for screen readers
markersThe markers, as on locations (without distance)
htmlThe result cards as <li class="sib-card"> items, one per marker: the markup the split layout prints

The response carries X-Robots-Tag: noindex and no paging headers.

ErrorWhen
400 sibooking_map_invalid_boundsbounds is not four numbers, a value is out of range, or south is above north
400 sibooking_map_invalid_datesWith search=1: from or to is missing or not Y-m-d, to is before from, or len is not a length
400 rest_invalid_paramA value outside its list or range: search, period, pins, limit, qty, guests, children, infants, pets
401 / 403 rest_forbiddenNot signed in, or the user lacks manage_woocommerce. A WooCommerce key answers 401 here as well (invalid_username)

The rentals inside part of the map that are free for a weekend for two people:

curl -u "username:YOUR_APP_PASSWORD" \
  "$STORE/sibooking/v1/map?bounds=44.85,-93.40,45.00,-93.20&from=2026-12-04&to=2026-12-06&guests=2"
{
  "count": 2,
  "count_label": "2 rentals",
  "region_label": "Map of 2 rental locations",
  "markers": [
    {"id": 456, "name": "Lakeside cabin", "lat": "44.920700", "lng": "-93.301900",
     "approximate": true, "radius": 500, "rental": true, "available": true, "...": "..."},
    {"id": 123, "name": "Two-person kayak", "lat": "44.909700", "lng": "-93.241000",
     "approximate": false, "radius": 0, "rental": true, "available": false,
     "unavailable_label": "Not available for your dates", "...": "..."}
  ],
  "html": "<li class=\"sib-card\" data-marker=\"456\">...</li><li class=\"sib-card is-unavailable\" data-marker=\"123\">...</li>"
}

The messaging webhook #

The address your SMS or WhatsApp provider calls to report what happened to a reminder (delivered or failed) and to pass on a customer’s reply (STOP, START, HELP). You don’t call it yourself: you paste it into the provider’s console. Setting up text reminders is in SMS and WhatsApp rental reminders.

https://example.com/wp-json/sibooking/v1/messaging/webhook/{provider}
ProviderWhat it sendsHow it is checked
twilioDelivery receipts, and incoming SMS and WhatsApp messages (POST, form-encoded)X-Twilio-Signature: Twilio’s signature over the full URL and the posted fields, made with your Auth Token. There is no GET handshake
meta_whatsappWhatsApp delivery statuses, customer messages, and WhatsApp’s own stop and resume choice (POST, JSON)X-Hub-Signature-256: Meta’s sha256= signature over the raw body, made with your App secret. Plus the GET handshake below
logNothing: the test provider only writes to the WooCommerce logIt takes no webhooks, so every call is refused
A provider added with the sibooking_messaging_providers filterWhat its class handlesIts own signature check. A provider without webhooks refuses every call

Where to paste it.

  • Twilio. Delivery receipts need no setup: each message the plugin sends carries its own status callback (.../webhook/twilio?message_id=123), as long as the site has a public address. For replies, set the Twilio phone number’s A message comes in webhook (or your Messaging Service’s incoming-message webhook) to https://example.com/wp-json/sibooking/v1/messaging/webhook/twilio, method HTTP POST.
  • WhatsApp from Meta. Rentals > Settings > Text messages shows the Callback URL (.../webhook/meta_whatsapp) and a Verify token, each with a Copy button. In your Meta app, open WhatsApp > Configuration > Webhook, paste both, and subscribe to the messages and user_preferences fields. The full steps are in WhatsApp via Meta.

Signatures. A POST without a signature, with a wrong one, or sent before the provider’s Auth Token or App secret is saved under Text messages, is refused with 403. Twilio signs the exact URL it called, so the site must see the request with the same scheme and host: behind a proxy that ends HTTPS, WordPress has to recognize the request as HTTPS, or every Twilio call is refused.

The handshake (Meta). Before Meta sends anything, it checks the URL with a GET carrying hub.mode=subscribe, hub.challenge and hub.verify_token. When the token matches the Verify token on the settings screen and the challenge is letters, digits, _ or - (up to 255 characters), the route answers 200 with the challenge as plain text and nothing else. Anything else answers 403, without saying which part was wrong. Twilio needs no handshake.

What a signed request does.

  • A delivery status updates the reminder’s record (delivered or failed, with the provider’s reason). For a WhatsApp reminder, a status code that means the customer blocked the business records an opt-out, and on a reminder set to WhatsApp then SMS, a failure that means the number is not on WhatsApp sends the SMS instead.
  • STOP, or another stop word (QUIT, END, CANCEL, UNSUBSCRIBE and their equivalents in the plugin’s languages), records an opt-out for every channel and cancels the texts still queued for that number. A carrier-level opt-out reported by the provider does the same.
  • START (or UNSTOP, YES) records consent again.
  • HELP or INFO is recorded and answered with the help text, unless text messages are in dry-run mode.
  • Any other reply is kept for staff to read (the last 50) and fires sibooking_messaging_inbound_unmatched.
AnswerWhen
200 {"ok": true}A signed POST was accepted
200, plain textThe GET handshake passed: the body is the challenge
403 {"error": "signature"}A POST without a valid signature, or to a provider that takes no webhooks
403 {"error": "verification failed"}The GET handshake failed, or the provider has none
404 {"error": "unknown provider"}No provider has that id
404 rest_no_routeAny method other than GET and POST

These answers are not the WordPress error shape: the body is {"error": "..."} or {"ok": true}. If your provider’s console shows 403 for every call, check the Auth Token or App secret saved under Text messages and that the URL matches the one the provider calls exactly.

Hooks #

  • sibooking_map_markers filters the markers; its context’s source is rest for locations and map for map.
  • sibooking_map_max_markers (the 500 ceiling), sibooking_map_price_totals (turn total off), sibooking_map_card_facts and sibooking_map_card_image_count shape the markers.
  • sibooking_product_location is the location every caller sees; sibooking_location_privacy, sibooking_location_privacy_default and sibooking_location_privacy_radius decide exact or approximate and the circle.
  • sibooking_messaging_providers registers a provider (its id becomes the {provider} part of the webhook URL); sibooking_messaging_inbound_unmatched fires for a reply that matched no keyword, with the number, the text and the channel.