These hooks change how HappenBoard offers and records appointments: which times can be booked, who hosts them, what they cost, how they are paid and what happens at each step. Reach for them to block times from another system, add a room picker, price by time of day or sync bookings to a CRM.
If you are new to HappenBoard hooks, start with Extending HappenBoard with hooks. The feature itself is explained in Appointment booking: overview. The emails these actions send have their own notification and email hooks.
Booking statuses and the actions they fire
Each booking is saved with a status, then moves on when it is paid, approved, declined, cancelled or moved. The table lists the actions fired at each step, in order, without their happenboard_booking_ prefix.
| What happens | Status saved | Actions, in order |
|---|---|---|
| Booked, nothing to approve or pay | confirmed | created, confirmed |
| Request on a type that needs approval | pending_approval | created, requested |
| Paid online (Pro) | pending_payment, then confirmed | created, payment_pending, then paid, confirmed |
| Request with the card held (Pro) | pending_payment, then pending_approval | created, payment_pending, then payment_authorized, requested |
| Request approved | confirmed | approved, confirmed, then payment_captured if a held card was charged |
| Request declined | declined | declined, cancelled |
| Booking cancelled | cancelled | cancelled |
| Checkout never completed | cancelled | payment_abandoned |
| Booking moved | unchanged | rescheduled |
| Marked as completed or no-show, once started | completed or no_show | none |
How offered times are checked
- The Booking Page asks for one month of times at a time. One request covers at most 186 days and returns at most 750 slots.
- When a booking is made or moved, HappenBoard runs the engine again for that day, uncached, and refuses a start it no longer offers. So
happenboard_booking_slotsdecides what can be booked, not only what is shown. - That check and the write run under one lock for the whole site, so two visitors picking the same time are served one after the other.
- A visitor who waits more than 10 seconds for the lock is asked to try again (
happenboard_booking_busy, HTTP 503). Keep the filters that run during the check fast, without remote requests. - A host is busy during every booking they hold, of any booking type, widened by that booking type’s buffers.
- Seats are shared only by bookings of the same type that start at the same time. Day-based types and types where the customer picks the length share their capacity across overlapping bookings.
- A booking type’s daily cap hides a host for a day once the bookings they start that day, in the site’s timezone, reach the cap.
- Slot lists are cached only when the site has a persistent object cache. A browser or CDN may reuse an anonymous visitor’s list for 60 seconds.
Prices and online payment
The free plugin shows a price per seat and takes payment on site. What a booking costs is worked out once by happenboard_booking_price, then stored on the booking with its currency. Emails, the Bookings screen and the checkout read that stored amount, even if prices change later.
HappenBoard Pro takes payment through Stripe. It answers happenboard_booking_awaiting_payment (pay now) or, for a request, happenboard_booking_authorize_payment (hold the card until you approve). It then sends the customer to Stripe through happenboard_booking_payment_url and charges a held card through happenboard_booking_capture_payment.
When less than the price is taken, the booking is recorded as a deposit, with the rest due on site. How long Pro keeps a card held before declining the request is set by happenboard_pro_booking_hold_hours.
happenboard_booking_slots
Filters the free times of a booking type, after availability rules, notice limits, buffers, daily caps and capacity are applied. Every offered time passes here, so it is the one place to add or remove bookable slots.
Signature
apply_filters( 'happenboard_booking_slots', array $list, int $booking_type_id, string $from, string $to, array $context )
Parameters
-
$listarray<int,array<string,mixed>> - Slots sorted by start. Each has
start_utcandend_utc(UTCY-m-d H:i:s),start(ISO 8601 in the visitor’s timezone), the display stringslabel,day_labelandtime_label,hosts(user IDs free then),host_seats(host ID to seats left) andcapacity_left(the most seats any host has). -
$booking_type_idint - The booking type (
hboard_booking_typepost ID). -
$fromstring - First day of the window, as the engine received it (a site-timezone
Y-m-don the public route). -
$tostring - Last day of the window.
-
$contextarray{length:int,ignore_booking_id:int} length: how many of the type’s durations each slot runs.ignore_booking_id: a booking being moved, whose own seats must not count, or0.
Return value
array<int,array<string,mixed>>
A zero-indexed list of slots.
Example
add_filter(
'happenboard_booking_slots',
function ( $slots, $booking_type_id ) {
if ( 42 !== $booking_type_id ) {
return $slots;
}
// No bookings on Friday afternoons, site time.
$slots = array_filter(
$slots,
function ( $slot ) {
$local = ( new DateTimeImmutable( $slot['start_utc'], new DateTimeZone( 'UTC' ) ) )->setTimezone( wp_timezone() );
return ! ( '5' === $local->format( 'N' ) && (int) $local->format( 'G' ) >= 12 );
}
);
return array_values( $slots );
},
10,
2
);
Notes
- Up to five arguments: use
add_filter( ..., 10, 5 )to receive$context. A callback that subtracts its own usage should leave out the booking named inignore_booking_id, as the engine does. - It also decides what can be written. Making or moving a booking runs the engine again, uncached, and refuses a start missing from your list. A slot you add needs
start_utcin UTCY-m-d H:i:s, real user IDs inhostsand enough seats inhost_seatsorcapacity_left. - Return a list: finish with
array_values(). A list with gaps reaches the browser as a JSON object and breaks the Booking Page. - On the write path the window is one day and the display strings are in UTC, so base your logic on
start_utc. HappenBoard Pro hooks in at priority 10 (shared resources), 20 (external calendars) and 30 (a price per slot): use a priority above 30 to see the final list.
happenboard_slot_cache_ttl
Filters how long the public slot list of a booking type is cached, in seconds. By default lists are cached for 5 minutes when the site has a persistent object cache, and not at all otherwise.
Signature
apply_filters( 'happenboard_slot_cache_ttl', int $ttl )
Parameters
-
$ttlint 300with a persistent object cache,0without.
Return value
int
Seconds to cache. 0 turns caching off, for reads and writes.
Example
add_filter(
'happenboard_slot_cache_ttl',
function ( $ttl ) {
// A busy shared calendar: keep lists one minute at most.
return $ttl > 0 ? MINUTE_IN_SECONDS : 0;
}
);
Notes
- Without a persistent object cache, a positive value stores each list in
wp_options(often 60 to 230 KB per entry). Those rows stay behind after every booking change, until WordPress deletes expired transients. Keep 0 there. - The cache only speeds up display. The time a visitor picks is checked again, uncached, when the booking is written: a stale list can offer a time that is then refused, never double-book it.
- Every list is dropped when a booking is created, confirmed, cancelled, moved or released unpaid, and when a booking type or its settings change. Entries are keyed by booking type, window, timezone, language and length.
- The cached list is the one returned by
happenboard_booking_slots, so your slot filter’s answer is cached too.
happenboard_slots_max_age
Filters how long, in seconds, a browser or CDN may reuse the slot list sent to an anonymous visitor. The public slots route sends Cache-Control: public, max-age=60 by default.
Signature
apply_filters( 'happenboard_slots_max_age', int $max_age )
Parameters
-
$max_ageint 60by default.
Return value
int
Seconds. 0 sends no Cache-Control header.
Example
add_filter(
'happenboard_slots_max_age',
function () {
// Our CDN caches REST answers: keep slot lists fresher.
return 15;
}
);
Notes
- Applies to
GET /happenboard/v1/booking-types/{id}/slotsfor visitors who are not logged in. Logged-in requests, the times offered to move a booking and a 429 refusal are never marked cacheable. - A reused list can still show a time someone has just booked. The booking is checked again when written, so that visitor is told the time is gone and nobody is double-booked.
- A CDN is not purged when a booking is made: this value is the longest a reused list can lag behind.
happenboard_booking_location_labels
Filters the words shown for a booking type’s location mode, such as “In person”. Use it to name the custom mode, which has no label by default, or to rename the others.
Signature
apply_filters( 'happenboard_booking_location_labels', array $labels )
Parameters
-
$labelsarray<string,string> - Mode slug to label:
in_person(In person),virtual(Online),phone(By phone).
Return value
array<string,string>
The labels. A mode without one shows no location.
Example
add_filter(
'happenboard_booking_location_labels',
function ( $labels ) {
$labels['custom'] = 'At your premises';
return $labels;
}
);
Notes
- Used by the Booking Page and Booking Availability blocks, and by the results of HappenBoard Pro’s Find a Time block.
- Labels are escaped when printed: return plain text.
happenboard_booking_page_choices
Adds pickers such as “Room” or “Table” to the Booking Page block, shown before the visitor picks a time. Each picker narrows the times shown and is sent with the booking.
Signature
apply_filters( 'happenboard_booking_page_choices', array $choices, array $config )
Parameters
-
$choicesarray<int,array<string,mixed>> - Pickers so far. On a
fixedbooking type with several hosts, the free plugin adds a host picker (keyhost, fieldhosts). -
$configarray<string,mixed> - Resolved booking-type config, including
id.
Return value
array<int,array<string,mixed>>
Pickers, each with key (sent as choices[key]), field (the slot key listing the IDs free at that time), label, any (the no-preference label, value 0) and options (value and label pairs).
Example
add_filter(
'happenboard_booking_page_choices',
function ( $choices, $config ) {
if ( 42 !== (int) $config['id'] ) {
return $choices;
}
$choices[] = array(
'key' => 'table',
'field' => 'free_tables', // Added to each slot by your slots filter.
'label' => 'Table',
'any' => 'Any table',
'options' => array( array( 'value' => 1, 'label' => 'Window' ), array( 'value' => 2, 'label' => 'Terrace' ) ),
);
return $choices;
},
10,
2
);
Notes
- Two arguments: register with
add_filter( ..., 10, 2 ). - A time is shown only when its slot lists the chosen ID under
field: add that list to each slot inhappenboard_booking_slots. - Check and settle the posted value in
happenboard_booking_choices: anyone can post any value to the REST route. - HappenBoard Pro adds its resource picker here (key
resource, fieldresources) when a booking type offers the customer a choice of two or more active resources.
happenboard_booking_person_types
Defines the person types a booking type takes, such as Adult and Child, so visitors book “2 × Adult, 1 × Child” instead of a seat count. The free plugin defines none; HappenBoard Pro returns the types set on the booking type.
Signature
apply_filters( 'happenboard_booking_person_types', array $types, array $config )
Parameters
-
$typesarray<int,array<string,mixed>> - Empty by default.
-
$configarray<string,mixed> - Resolved booking-type config, including
id.
Return value
array<int,array<string,mixed>>
Types, each with key, label, min, max (0 for no limit of its own) and seats (whether each person takes a seat).
Example
add_filter(
'happenboard_booking_person_types',
function ( $types, $config ) {
if ( array() !== $types || 42 !== (int) $config['id'] ) {
return $types;
}
return array(
array( 'key' => 'adult', 'label' => 'Adult', 'min' => 1, 'max' => 0, 'seats' => true ),
array( 'key' => 'child', 'label' => 'Child', 'min' => 0, 'max' => 4, 'seats' => true ),
// Infants sit on a lap: no seat taken.
array( 'key' => 'infant', 'label' => 'Infant', 'min' => 0, 'max' => 2, 'seats' => false ),
);
},
5,
2
);
Notes
- Two arguments: register with
add_filter( ..., 10, 2 ). HappenBoard Pro answers at priority 10 and keeps a non-empty list it receives, so a lower priority wins. - The list is cleaned: at most 10 types, counts up to 50, labels up to 60 characters, and
seatstrue unless set to false. - Once a booking type has person types, a visitor’s booking must send counts or it is refused (
happenboard_booking_persons_invalid, HTTP 400). The seats booked become the people who take a seat, at least 1. - The breakdown is stored on the booking, labels included, and passed to
happenboard_booking_priceandhappenboard_booking_choices. The free plugin still prices per seat; prices per type come from Pro.
happenboard_booking_price
Filters what a booking costs, in minor units of the site currency (cents for USD or EUR). The free plugin’s answer is the booking type’s price times the seats booked.
Signature
apply_filters( 'happenboard_booking_price', int $price, array $config, array $booking )
Parameters
-
$priceint - Flat price times seats, in minor units.
-
$configarray<string,mixed> - Resolved booking-type config.
-
$bookingarray{start_utc:string,host_user_id:int,quantity:int,length:int,persons:array<string,int>} start_utc,host_user_id(0 when no host is chosen yet),quantity(seats),length(how many durations, 1 on a fixed-length type) andpersons(person type key to count).
Return value
int
The price in minor units. A negative value becomes 0.
Example
add_filter(
'happenboard_booking_price',
function ( $price, $config, $booking ) {
$start = new DateTimeImmutable( $booking['start_utc'], new DateTimeZone( 'UTC' ) );
// 20% evening supplement from 18:00, site time.
if ( (int) $start->setTimezone( wp_timezone() )->format( 'G' ) >= 18 ) {
$price = (int) round( $price * 1.2 );
}
return $price;
},
20,
3
);
Notes
- Three arguments: register with
add_filter( ..., 10, 3 ). - The result is stored on the booking (
price_centsandcurrency) when it is made. Emails, the Bookings screen and the checkout use that amount, even if your rule changes later. - The public quote route (
GET /happenboard/v1/booking-types/{id}/quote) asks it too, so the Booking Page shows the amount the booking is then written with. Keep it free of side effects. - HappenBoard Pro applies its pricing rules at priority 10 and, with person types, prices each type and ignores the incoming amount. Hook later than 10 to adjust its result.
happenboard_booking_price_label
Filters the headline price shown for a booking type before a time is picked, in the Booking Page, Booking Availability and Booking Types blocks.
Signature
apply_filters( 'happenboard_booking_price_label', string $label, array $config )
Parameters
-
$labelstring - The flat price formatted in the site currency, or
''when it is 0. -
$configarray<string,mixed> - Resolved booking-type config.
Return value
string
Plain text, or '' to show no price.
Example
add_filter(
'happenboard_booking_price_label',
function ( $label, $config ) {
// A service priced after a first consultation.
return 42 === (int) $config['id'] ? 'On quote' : $label;
},
20,
2
);
Notes
- Two arguments: register with
add_filter( ..., 10, 2 ). - It changes the words only. The amount owed or charged comes from
happenboard_booking_price. - HappenBoard Pro returns a “From” price at priority 10 when its pricing rules make the price depend on the time.
- The blocks escape the label: return plain text, not HTML.
happenboard_currency
Filters the site currency, as a three-letter ISO 4217 code. It is the currency of every booking price HappenBoard shows and records.
Signature
apply_filters( 'happenboard_currency', string $code )
Parameters
-
$codestring - The currency saved in HappenBoard’s general settings,
USDby default.
Return value
string
A three-letter code. Anything else falls back to USD.
Example
add_filter(
'happenboard_currency',
function ( $code ) {
// Follow the WooCommerce store currency when WooCommerce is active.
return function_exists( 'get_woocommerce_currency' ) ? get_woocommerce_currency() : $code;
}
);
Notes
- Nothing is converted: switching currency relabels the prices of your booking types.
- Each booking stores the currency it was made in and keeps showing it, so a later switch does not relabel bookings already made.
- HappenBoard Pro turns online booking payment off when its Stripe currency uses a different number of decimals than this one (USD and JPY, for example).
- Read the currency with
\HappenBoard\Service\Money::currency(), not the raw setting, so your code and the plugin agree.
happenboard_booking_client_ip
Filters the visitor IP address that the public booking routes count requests against. Use it behind a reverse proxy or CDN, where every visitor otherwise arrives from the proxy’s address.
Signature
apply_filters( 'happenboard_booking_client_ip', string $ip )
Parameters
-
$ipstring REMOTE_ADDRwhen it is a valid IP, else''.
Return value
string
An IP address, or '' to count nothing.
Example
add_filter(
'happenboard_booking_client_ip',
function ( $ip ) {
// Behind Cloudflare: use the visitor address it forwards.
$real = filter_var( $_SERVER['HTTP_CF_CONNECTING_IP'] ?? '', FILTER_VALIDATE_IP );
return $real ? $real : $ip;
}
);
Notes
- Read by booking submissions (8 a minute per address, 5 an hour per email) and HappenBoard Pro’s Find a Time search (20 a minute). Slot and quote lookups allow 60 a minute, counted only with a persistent object cache unless
happenboard_throttle_public_readssays otherwise. - On a submission, the same address is sent to Cloudflare Turnstile and Akismet when they are turned on.
- Only trust a header your proxy sets. If the site can also be reached directly, anyone can send that header and choose their own address.
- Returning
''turns the per-address limits off. RSVPs and ticket checkouts readhappenboard_rsvp_client_ipinstead: add the same callback there.
happenboard_booking_choices
Checks and settles the extension choices of a booking about to be written, such as a room picked on the Booking Page. Return a WP_Error to refuse the booking or the move.
Signature
apply_filters( 'happenboard_booking_choices', array $choices, array $context, array $slot )
Parameters
-
$choicesarray<string,int>|WP_Error - Choice key to ID from the booking request (0 means no preference). Empty when a booking is moved. May be a
WP_Errorfrom an earlier callback. -
$contextarray<string,mixed> action(createorreschedule),booking_type_id,booking_id(0 on create),start_utc,end_utc,host_user_id,quantity,lengthandpersons.-
$slotarray<string,mixed> - The live slot, never a cached one, with whatever your
happenboard_booking_slotscallback added.
Return value
array<string,int>|WP_Error
The settled choices, or a WP_Error with a status such as 409.
Example
add_filter(
'happenboard_booking_choices',
function ( $choices, $context, $slot ) {
if ( is_wp_error( $choices ) || ! isset( $choices['table'] ) ) {
return $choices;
}
// 'free_tables' was added to each slot by your slots filter.
$free = array_map( 'intval', (array) ( $slot['free_tables'] ?? array() ) );
$wanted = (int) $choices['table'];
if ( array() === $free || ( $wanted > 0 && ! in_array( $wanted, $free, true ) ) ) {
return new WP_Error( 'acme_table_taken', 'That table is no longer free.', array( 'status' => 409 ) );
}
// Settle "no preference" on the first free table.
$choices['table'] = $wanted > 0 ? $wanted : $free[0];
return $choices;
},
10,
3
);
Notes
- Three arguments: register with
add_filter( ..., 10, 3 ). Pass on aWP_Erroryou receive. - It runs inside the booking write lock, after the free plugin’s own checks: keep it fast.
- The free plugin does not store choices. The settled values reach
happenboard_booking_created(third argument) andhappenboard_booking_rescheduled(fourth): save them there. - The public route accepts up to 10 choices, keys through
sanitize_key()and IDs as whole numbers. HappenBoard Pro settles itsresourcechoice here at priority 10.
happenboard_booking_assignment_strategy
Replaces the object that picks the host of a new booking among the hosts free at that time. It also runs when a moved booking’s host is not free at the new time.
Signature
apply_filters( 'happenboard_booking_assignment_strategy', \HappenBoard\Service\Booking\Assignment\Strategy $strategy, string $assignment )
Parameters
-
$strategy\HappenBoard\Service\Booking\Assignment\Strategy LeastLoaded(the host with the fewest upcoming bookings) forround_robin,Fixed(the chosen host, else the first free one) for any other mode.-
$assignmentstring - The booking type’s mode:
fixedorround_robin, the two the booking type editor offers.
Return value
\HappenBoard\Service\Booking\Assignment\Strategy
An object whose pick( array $candidate_host_ids, int $booking_type_id, string $slot_utc ) returns a user ID, or 0 for none.
Example
add_filter(
'happenboard_booking_assignment_strategy',
function ( $strategy, $assignment ) {
if ( 'round_robin' !== $assignment ) {
return $strategy;
}
// Give the booking to host 12 whenever they are free.
return new class( $strategy ) implements \HappenBoard\Service\Booking\Assignment\Strategy {
public function __construct( private $fallback ) {}
public function pick( array $candidate_host_ids, int $booking_type_id, string $slot_utc ): int {
return in_array( 12, $candidate_host_ids, true )
? 12
: $this->fallback->pick( $candidate_host_ids, $booking_type_id, $slot_utc );
}
};
},
10,
2
);
Notes
- Two arguments: register with
add_filter( ..., 10, 2 ). Returning anything other than aStrategythrows aTypeError. - Candidates are the hosts with enough seats free at that time, or only the host the visitor chose when that host is free. The ID
pick()returns is not checked again: return a candidate, or 0 to refuse the booking (happenboard_booking_no_host, HTTP 409). - On a move, a
fixedbooking type never reaches it: the move is refused when the booking’s own host is busy (happenboard_booking_host_unavailable, HTTP 409). - Only
round_robinhas a strategy of its own. Other stored modes (collective,weighted,priority) behave likefixed, and HappenBoard Pro adds no strategy today.
happenboard_booking_awaiting_payment
Decides whether a booking must be paid online before it counts. The free plugin answers no (payment on site); HappenBoard Pro answers yes when online booking payment is on and Stripe is set up.
Signature
apply_filters( 'happenboard_booking_awaiting_payment', bool $awaiting, int $booking_type_id, array $config, int $price_cents )
Parameters
-
$awaitingbool falseby default.-
$booking_type_idint - The booking type being booked.
-
$configarray<string,mixed> - Resolved booking-type config, including
price_cents(flat price per seat) anddeposit_cents. -
$price_centsint - What this booking costs, from
happenboard_booking_price.
Return value
bool
True to write the booking as pending_payment, holding its time while the customer pays.
Example
add_filter(
'happenboard_booking_awaiting_payment',
function ( $awaiting, $booking_type_id, $config, $price_cents ) {
// Small amounts are paid on site, even with online payment on.
return $awaiting && $price_cents >= 5000;
},
20,
4
);
Notes
- Four arguments: register with
add_filter( ..., 10, 4 ). - Asked only for a priced booking made by a visitor, on a booking type that does not need approval. Requests go through
happenboard_booking_authorize_paymentinstead. - The Booking Page and the quote route ask it too, to say how the price will be paid: keep it free of side effects.
- Returning true with no URL from
happenboard_booking_payment_urlreleases the time at once, and the visitor getshappenboard_booking_payment_unavailable(HTTP 502).
happenboard_booking_authorize_payment
Decides whether a booking request, on a type that needs approval, must have the customer’s card held online before it reaches your approval queue. The card is charged when you approve and released when you decline.
Signature
apply_filters( 'happenboard_booking_authorize_payment', bool $authorize, int $booking_type_id, array $config, int $price_cents )
Parameters
-
$authorizebool falseby default.-
$booking_type_idint - The booking type being booked.
-
$configarray<string,mixed> - Resolved booking-type config, including
price_cents(flat price per seat) anddeposit_cents. -
$price_centsint - What this booking costs, from
happenboard_booking_price.
Return value
bool
True to send the customer to a checkout that holds the card. The booking holds its time as pending_payment meanwhile.
Example
add_filter(
'happenboard_booking_authorize_payment',
function ( $authorize, $booking_type_id, $config, $price_cents ) {
// Only hold a card for requests worth 100.00 or more.
return $authorize && $price_cents >= 10000;
},
20,
4
);
Notes
- Four arguments: register with
add_filter( ..., 10, 4 ). - Asked only for a priced request made by a visitor. A booking entered by an operator is confirmed at once: the operator is the approval.
- Kept apart from
happenboard_booking_awaiting_paymenton purpose: a provider that can only charge outright would otherwise charge requests in full. - HappenBoard Pro answers it with the same rule as
happenboard_booking_awaiting_payment. The Booking Page and quote route ask it too: keep it free of side effects.
happenboard_booking_payment_url
Filters the address a customer is sent to after a booking is written as pending_payment, to pay or to have their card held. HappenBoard Pro returns a Stripe Checkout URL.
Signature
apply_filters( 'happenboard_booking_payment_url', string $url, int $booking_id, array $booking )
Parameters
-
$urlstring - Empty by default.
-
$booking_idint - Booking ID.
-
$bookingarray<string,mixed> - The saved booking,
cancel_tokenremoved.payment_statusispending(charge now) orauthorizing(hold the card only);price_centsis what it costs.
Return value
string
An absolute URL, or ''.
Example
add_filter(
'happenboard_booking_payment_url',
function ( $url, $booking_id, $booking ) {
// Keep an earlier answer, else build our own checkout link.
return '' !== $url ? $url : acme_checkout_link( $booking_id, (int) $booking['price_cents'] );
},
20,
3
);
Notes
- Up to three arguments: register with
add_filter( ..., 10, 3 )to receive the row. - An empty answer cancels the booking at once, puts the time back on offer and answers the visitor with
happenboard_booking_payment_unavailable(HTTP 502). No action fires. - For
authorizing, the checkout must only hold the card (Stripe’scapture_methodmanual), never charge it. - HappenBoard Pro’s checkout expires after 31 minutes. Its webhook then frees the time and
happenboard_booking_payment_abandonedfires.
happenboard_booking_capture_payment
Charges the card held for a booking request when you approve it. The request is confirmed only once this reports the money taken.
Signature
apply_filters( 'happenboard_booking_capture_payment', array|WP_Error|null $result, int $booking_id, array $booking )
Parameters
-
$resultarray<string,mixed>|WP_Error|null nullby default, or an earlier callback’s answer.-
$booking_idint - Booking ID.
-
$bookingarray<string,mixed> - The booking,
cancel_tokenremoved.payment_intentholds the provider reference saved with the hold,paid_centsthe amount held.
Return value
array<string,mixed>|WP_Error|null
array( 'amount' => int ) once charged (in minor units), a WP_Error saying why not, or null when nothing can charge it.
Example
add_filter(
'happenboard_booking_capture_payment',
function ( $result, $booking_id, $booking ) {
$reference = (string) ( $booking['payment_intent'] ?? '' );
// Leave answered results, and holds from other gateways, alone.
if ( null !== $result || ! str_starts_with( $reference, 'acme_' ) ) {
return $result;
}
$charge = acme_gateway_capture( $reference ); // Must be safe to retry.
return is_wp_error( $charge ) ? $charge : array( 'amount' => (int) $charge['amount'] );
},
5,
3
);
Notes
- Three arguments: register with
add_filter( ..., 10, 3 ). HappenBoard Pro captures the Stripe hold at priority 10: pass a non-null$resulton unless you mean to replace it. null: the approval is refused (HTTP 503) and nothing changes. AWP_Error: the request stays in the queue, a note records the reason and the approval answers HTTP 402. An array: the booking is confirmed.- Without
amount, the held amount is recorded as taken. Less thanprice_centsis stored as a deposit. - After a failure the operator can approve again, which asks this filter again: make the charge safe to retry.
happenboard_booking_payment_hold_hours
Filters how long an unpaid booking may hold its time before the hourly clean-up releases it. It is the safety net for a payment webhook that never arrives.
Signature
apply_filters( 'happenboard_booking_payment_hold_hours', int $hours )
Parameters
-
$hoursint 24by default.
Return value
int
Hours. Values below 1 become 1.
Example
add_filter(
'happenboard_booking_payment_hold_hours',
function () {
// Never shorter than the payment provider's checkout session.
return 6;
}
);
Notes
- Never set it below your provider’s checkout lifetime, or a customer can pay for a time already given away. HappenBoard Pro’s Stripe checkout lasts 31 minutes.
- Age is counted from when the booking was made, not from its start. Each run releases up to 500 bookings still
pending_paymentand fireshappenboard_booking_payment_abandonedfor each. - It runs on WP-Cron (
happenboard_release_payment_holds, hourly), so a site with little traffic releases late. - Not to be confused with
happenboard_pro_booking_hold_hours, which declines requests whose card has been held too long.
happenboard_booking_created
Fires after a new booking is saved, whatever its status. Use it to sync bookings to a CRM or create a meeting link.
Signature
do_action( 'happenboard_booking_created', int $booking_id, array $data, array $choices )
Parameters
-
$booking_idint - Booking ID.
-
$dataarray<string,mixed> - The saved row, with
cancel_tokenremoved (never pass that secret on: anyone holding it can cancel or move the booking). -
$choicesarray<string,int> - Extension choices as settled by
happenboard_booking_choices.
Example
add_action(
'happenboard_booking_created',
function ( $booking_id, $data, $choices ) {
wp_remote_post(
'https://crm.example.com/bookings',
array(
'timeout' => 5,
'headers' => array( 'Content-Type' => 'application/json' ),
'body' => wp_json_encode( array(
'id' => (int) $booking_id,
'status' => (string) $data['status'],
'start' => (string) $data['start_utc'],
'email' => (string) $data['customer_email'],
) ),
)
);
},
10,
3
);
Notes
- Three arguments: register with
add_action( ..., 10, 3 ). statuscan beconfirmed,pending_approvalorpending_payment. For real appointments only, listen onhappenboard_booking_confirmed, which also covers bookings confirmed later.- Values arrive as database strings: cast before comparing, and read
personswith\HappenBoard\Service\Booking\PersonTypes::from_row(). - Already hooked: HappenBoard Pro at priority 5 saves the resource in
$choices; the free notifier at 10 sends the confirmation or “request received” email, and nothing yet for a booking awaiting payment.
happenboard_booking_payment_pending
Fires when a new booking holds its time while the customer pays online, or while their card is held for a request.
Signature
do_action( 'happenboard_booking_payment_pending', int $booking_id )
Parameters
-
$booking_idint - Booking ID.
Example
add_action(
'happenboard_booking_payment_pending',
function ( $booking_id ) {
acme_metrics( 'booking_checkout_started', $booking_id );
}
);
Notes
- The row’s
payment_statustells the two apart:pending(paying now) orauthorizing(card to be held). - It is not a commitment: when
happenboard_booking_payment_urlreturns nothing right after, the booking is cancelled on the spot and nothing more fires. - Named
_payment_pendingbecausehappenboard_booking_awaiting_paymentis a filter, and WordPress keeps actions and filters in one list.
happenboard_booking_requested
Fires when a booking request reaches your approval queue. Use it to alert whoever has to answer.
Signature
do_action( 'happenboard_booking_requested', int $booking_id )
Parameters
-
$booking_idint - Booking ID.
Example
add_action(
'happenboard_booking_requested',
function ( $booking_id ) {
acme_slack( sprintf( 'Booking request #%d is waiting for an answer.', $booking_id ) );
}
);
Notes
- Fires from two places: at creation for a request with nothing to pay online, and right after
happenboard_booking_payment_authorizedonce the customer’s card is held. happenboard_booking_confirmeddoes not fire for a request. It fires later, if you approve it.- Already hooked: the free notifier emails the host and the site admin that a request is waiting.
happenboard_booking_confirmed
Fires whenever a booking becomes confirmed, on every path: booked directly, paid online or approved. Listen here for anything that should happen once per real appointment.
Signature
do_action( 'happenboard_booking_confirmed', int $booking_id )
Parameters
-
$booking_idint - Booking ID.
Example
add_action(
'happenboard_booking_confirmed',
function ( $booking_id ) {
$booking = ( new \HappenBoard\Service\Booking\BookingRepository() )->find_by_id( (int) $booking_id );
if ( null === $booking ) {
return;
}
// Count confirmed appointments per host.
$host = (int) $booking['host_user_id'];
update_user_meta( $host, 'acme_confirmed_count', (int) get_user_meta( $host, 'acme_confirmed_count', true ) + 1 );
}
);
Notes
- It follows
happenboard_booking_created,happenboard_booking_paidorhappenboard_booking_approved, depending on the path. - Only the ID is passed. The row’s
payment_statussays how it is paid:none,on_site,paidordeposit. - Already hooked: the free notifier emails the host and the site admin. The customer’s confirmation goes out from
happenboard_booking_created,happenboard_booking_approvedorhappenboard_booking_paid.
happenboard_booking_approved
Fires when a booking request is approved and confirmed. For a request with a card held, it fires only once that card is charged.
Signature
do_action( 'happenboard_booking_approved', int $booking_id )
Parameters
-
$booking_idint - Booking ID.
Example
add_action(
'happenboard_booking_approved',
function ( $booking_id ) {
acme_log( 'booking_approved', $booking_id );
}
);
Notes
happenboard_booking_confirmedfires right after. Approving a booking that is not awaiting approval is refused (HTTP 409), so it never fires twice for one booking.- Operators approve with
PATCH /happenboard/v1/bookings/{id}andstatus=confirmed. It also fires when the payment provider reports a charge made elsewhere, such as from the Stripe dashboard. - Already hooked: the free notifier sends the customer the confirmation, with its calendar invite, and schedules the reminders.
happenboard_booking_paid
Fires when the online payment for a booking lands and the booking is confirmed. HappenBoard Pro reaches it from its Stripe webhook.
Signature
do_action( 'happenboard_booking_paid', int $booking_id )
Parameters
-
$booking_idint - Booking ID.
Example
add_action(
'happenboard_booking_paid',
function ( $booking_id ) {
acme_issue_receipt( $booking_id );
}
);
Notes
happenboard_booking_confirmedfires right after. A webhook delivered twice confirms once, so it fires once per booking.- The amount taken is stored in
paid_cents. Less thanprice_centsis recorded as a deposit (payment_statusdeposit), the rest due on site. - A held card charged on approval does not fire it: see
happenboard_booking_payment_captured. - Already hooked: the free notifier sends the confirmation email and schedules the reminders.
happenboard_booking_payment_authorized
Fires when the customer’s card is held for a booking request and the request reaches your approval queue. Nothing is charged yet.
Signature
do_action( 'happenboard_booking_payment_authorized', int $booking_id )
Parameters
-
$booking_idint - Booking ID.
Example
add_action(
'happenboard_booking_payment_authorized',
function ( $booking_id ) {
acme_slack( sprintf( 'Request #%d is waiting, card held.', $booking_id ) );
}
);
Notes
happenboard_booking_requestedfires right after. The booking is nowpending_approval, withpayment_statusauthorizedand the amount held inpaid_cents.- Already hooked: the free notifier sends the customer the “request received” email, held back while they were at the checkout.
happenboard_booking_payment_captured
Fires when the card held for a booking request has been charged and the booking confirmed.
Signature
do_action( 'happenboard_booking_payment_captured', int $booking_id )
Parameters
-
$booking_idint - Booking ID.
Example
add_action(
'happenboard_booking_payment_captured',
function ( $booking_id ) {
acme_issue_receipt( $booking_id );
}
);
Notes
- Fires last, after
happenboard_booking_approvedandhappenboard_booking_confirmed, whether you approved the request or the provider reported a charge made elsewhere. happenboard_booking_paiddoes not fire here, so the customer gets one confirmation email.- The amount is in
paid_cents. Less thanprice_centsis recorded as a deposit.
happenboard_booking_payment_failed
Fires the first time the card held for a booking request cannot be charged when you approve it. The request stays in the queue, its time still held.
Signature
do_action( 'happenboard_booking_payment_failed', int $booking_id, WP_Error $error )
Parameters
-
$booking_idint - Booking ID.
-
$errorWP_Error - Why the charge was refused, as returned by
happenboard_booking_capture_payment.
Example
add_action(
'happenboard_booking_payment_failed',
function ( $booking_id, $error ) {
error_log( sprintf( 'Charge failed for booking %d: %s', $booking_id, $error->get_error_message() ) );
},
10,
2
);
Notes
- Two arguments: register with
add_action( ..., 10, 2 ). - Once only: approving again retries the card, but a second failure fires nothing. Each failure still adds a note to the booking.
- Already hooked: the free notifier tells the customer their card could not be charged.
happenboard_booking_payment_released
Fires when the card held for a booking is released and nothing was taken. The booking’s payment_status becomes voided.
Signature
do_action( 'happenboard_booking_payment_released', int $booking_id )
Parameters
-
$booking_idint - Booking ID.
Example
add_action(
'happenboard_booking_payment_released',
function ( $booking_id ) {
acme_log( 'card_hold_released', $booking_id );
}
);
Notes
- HappenBoard Pro releases the card when a request with a held card is declined or cancelled, and when Stripe reports the hold cancelled or expired.
- Fires only when a card was really held (
payment_statusauthorizedorfailed), never for a paid booking. - No email is sent here: the customer hears about the decline or the cancellation itself.
happenboard_booking_payment_abandoned
Fires when a booking whose payment never arrived is released and its time goes back on offer. It covers a checkout to pay and a checkout to hold a card.
Signature
do_action( 'happenboard_booking_payment_abandoned', int $booking_id )
Parameters
-
$booking_idint - Booking ID.
Example
add_action(
'happenboard_booking_payment_abandoned',
function ( $booking_id ) {
acme_metrics( 'booking_checkout_abandoned', $booking_id );
}
);
Notes
- Two sources: HappenBoard Pro’s Stripe webhook when the checkout expires or a delayed payment fails, and the free plugin’s hourly clean-up for holds older than
happenboard_booking_payment_hold_hours. - The status becomes
cancelled, buthappenboard_booking_cancelleddoes not fire: the customer never had a booking, so no cancellation email goes out.
happenboard_booking_declined
Fires when a booking request is declined, just before happenboard_booking_cancelled. The booking’s status is now declined.
Signature
do_action( 'happenboard_booking_declined', int $booking_id )
Parameters
-
$booking_idint - Booking ID.
Example
add_action(
'happenboard_booking_declined',
function ( $booking_id ) {
acme_log( 'booking_declined', $booking_id );
}
);
Notes
- Reached when you decline a request (
PATCH /happenboard/v1/bookings/{id}withstatus=declined), when you cancel a request still awaiting approval, and when HappenBoard Pro declines a request whose card hold lapsed or is about to. - Already hooked: the free notifier sends the customer the “request declined” email and skips the cancellation email.
- When a card was held, HappenBoard Pro releases it on
happenboard_booking_cancelled, andhappenboard_booking_payment_releasedfollows.
happenboard_booking_cancelled
Fires when a booking is cancelled by the customer or an operator, and after every decline. Use it to free anything your extension reserved for the booking.
Signature
do_action( 'happenboard_booking_cancelled', int $booking_id )
Parameters
-
$booking_idint - Booking ID.
Example
add_action(
'happenboard_booking_cancelled',
function ( $booking_id ) {
$booking = ( new \HappenBoard\Service\Booking\BookingRepository() )->find_by_id( (int) $booking_id );
// Free the meeting room this extension booked, for both cases.
if ( null !== $booking && in_array( $booking['status'], array( 'cancelled', 'declined' ), true ) ) {
acme_release_room( (int) $booking_id );
}
}
);
Notes
- Read the row’s
statusto tell a cancellation (cancelled) from a declined request (declined). - Cancelling twice is refused (HTTP 409) and fires nothing. Customers cannot cancel after the booking type’s cancellation cutoff; operators can.
- It does not fire for a checkout never completed (
happenboard_booking_payment_abandoned) or for a booking released because no payment URL came back. - Already hooked: HappenBoard Pro at priority 5 releases a held card. The free notifier at 10 sends the cancellation email (not after a decline), tells the host and site admin, and clears the reminders.
happenboard_booking_rescheduled
Fires after a booking moves to a new time. The new time has passed the same slot, seat and host checks as a new booking.
Signature
do_action( 'happenboard_booking_rescheduled', int $booking_id, string $old_start, string $new_start, array $choices )
Parameters
-
$booking_idint - Booking ID.
-
$old_startstring - Previous start, UTC
Y-m-d H:i:s. -
$new_startstring - New start, UTC
Y-m-d H:i:s. The end is not passed: read it from the row. -
$choicesarray<string,int> - Extension choices as settled by
happenboard_booking_choicesfor the move.
Example
add_action(
'happenboard_booking_rescheduled',
function ( $booking_id, $old_start, $new_start ) {
// Both times are UTC 'Y-m-d H:i:s'.
acme_crm_update( $booking_id, array( 'start_utc' => $new_start, 'previous_start_utc' => $old_start ) );
},
10,
3
);
Notes
- Up to four arguments: register with
10, 3for the times, or10, 4to receive$choices. - The host can change: on a booking type that assigns hosts itself, a busy host is replaced by a free one. Read
host_user_idfrom the row. - Customers move their booking from the link in their emails (confirmed bookings and requests, before the cancellation cutoff). Operators use the
reschedule-bookingability, with no cutoff. - Already hooked: HappenBoard Pro at priority 5 moves the booking’s resource. The free notifier at 10 re-schedules the reminders, sends the customer a “booking moved” email and tells the host and site admin.
happenboard_rest_booking_response
Filters one booking as the admin Bookings screen receives it. Use it to add a field, usually shown as an extra column.
Signature
apply_filters( 'happenboard_rest_booking_response', array $serialized, array $row )
Parameters
-
$serializedarray<string,mixed> - The camelCase payload the screen reads, such as
id,status,startUtc,customerName,priceCentsandpaymentStatus. -
$rowarray<string,mixed> - The raw database row,
cancel_tokenincluded.
Return value
array<string,mixed>
The payload. Add keys; keep the ones the screen reads.
Example
add_filter(
'happenboard_rest_booking_response',
function ( $item, $row ) {
// Loaded for the whole page in happenboard_rest_bookings_prefetch.
$id = (int) $row['booking_id'];
$item['source'] = (string) ( $GLOBALS['acme_booking_sources'][ $id ] ?? acme_source_for_booking( $id ) );
return $item;
},
10,
2
);
Notes
- Two arguments: register with
add_filter( ..., 10, 2 ). - It runs for each row of the Bookings screen and of the CSV export, and for the booking returned when an operator books for a customer. The CSV keeps its own columns: a key you add does not appear there.
- To show the key as a column, add a DataViews field to
window.happenBoard.bookingsExtraFieldsin an admin script loaded before the Bookings screen. - Never copy
cancel_tokenfrom$rowinto the payload. HappenBoard Pro addsresourceNameat priority 10.
happenboard_rest_bookings_prefetch
Fires once per page of the admin Bookings list, before happenboard_rest_booking_response filters each row. Load the data of your extra column here, in one query for the whole page.
Signature
do_action( 'happenboard_rest_bookings_prefetch', array $rows, WP_REST_Request $request )
Parameters
-
$rowsarray<int,array<string,mixed>> - The raw rows of the page, values as strings.
-
$requestWP_REST_Request - The
GET /happenboard/v1/bookingsrequest, with its page and filters.
Example
add_action(
'happenboard_rest_bookings_prefetch',
function ( $rows ) {
// One query for the whole page instead of one per row.
$ids = array_map( 'intval', array_column( $rows, 'booking_id' ) );
$GLOBALS['acme_booking_sources'] = acme_sources_for_bookings( $ids );
}
);
Notes
- Two arguments: register with
add_action( ..., 10, 2 )to receive the request. - Only the Bookings list fires it. The CSV export and an operator’s booking for a customer run the response filter without it, so that filter must cope with rows you did not load.
- A page holds up to 200 rows (50 by default).
Next steps
- Booking payments, deposits and pricing rules: how site owners set prices, deposits and card holds.
- Approving booking requests: the approval queue behind the request and decline actions.
- Notification and email hooks: change the emails these actions send.