Attendees and sign-up hooks

HappenBoard fires a set of actions and filters around attendee registration, so you can add your own fields to the sign-up form, react to a new registration, or change how a request is identified.

Two of them work as a pair: happenboard_rsvp_form_fields renders an extra input, and happenboard_rsvp_custom_fields decides whether what the visitor typed is kept. Rendering a field without validating it stores nothing.

happenboard_attendee_deleted

Action Free Since 1.0 src/REST/Attendees.php

Fires once an attendee row has been permanently deleted through the admin, at the end of DELETE /attendees/{id}. Use it to drop whatever you store about that attendee on your side.

Signature

do_action( 'happenboard_attendee_deleted', int $attendee_id )

Parameters

$attendee_id int
Primary key of the row that was just deleted.

Example

add_action(
    'happenboard_attendee_deleted',
    function ( int $attendee_id ): void {
        global $wpdb;

        $wpdb->delete(
            $wpdb->prefix . 'my_plugin_badges',
            array( 'attendee_id' => $attendee_id ),
            array( '%d' )
        );
    }
);

Notes

  • A single argument is passed, so a plain add_action() with no priority or accepted-args count is enough.
  • The row is already gone when the action runs: the delete query executes first, so looking the id up in the attendees table returns nothing. Capture what you need earlier, for instance on happenboard_attendee_status_changed, or keep your own copy.
  • Only the REST delete route fires this. Cancelling a sign-up does not delete anything: it fires happenboard_rsvp_cancelled, and an admin status change fires happenboard_attendee_status_changed.
  • HappenBoard Pro already listens, at priority 10 to wipe the attendee timeline and at priority 20 to emit the attendee.deleted webhook. Your own callback does not disturb either.
  • The call has no docblock in the source. The argument documented here comes from reading the do_action() call itself.

happenboard_attendee_status_changed

Action Free Since 1.0 src/REST/Attendees.php

Fires when an administrator changes an attendee’s status through PATCH /attendees/{id}, for example moving someone from the waitlist to going, or marking them cancelled from the attendees list.

Signature

do_action( 'happenboard_attendee_status_changed', int $attendee_id, string $new_status, string $old_status )

Parameters

$attendee_id int
Primary key of the attendee row.
$new_status string
The status that was just written, taken from the request.
$old_status string
The status read from the row before the update. Empty string when the row had none.

Example

add_action(
    'happenboard_attendee_status_changed',
    function ( int $attendee_id, string $new_status, string $old_status ): void {
        if ( 'going' === $new_status && 'waitlist' === $old_status ) {
            error_log( sprintf( 'Attendee %d promoted from the waitlist.', $attendee_id ) );
        }
    },
    10,
    3
);

Notes

  • Three arguments are passed, so register with 10, 3. With the default of one you only get the id.
  • The new status comes second and the old one third. Both are plain strings, so swapping them produces silently wrong results rather than an error.
  • It fires after the database write, and only when that write succeeded. A failed update returns a 500 from the endpoint and the action never runs.
  • Setting the status to cancelled from the admin fires this hook, not happenboard_rsvp_cancelled. Listen to both if you want every cancellation, whoever triggered it.
  • The call has no docblock. HappenBoard Pro consumes it with three arguments in both its timeline listener (priority 10) and its webhook catalogue (priority 20), which confirms the order.

happenboard_attendee_updated

Action Free Since 1.0 src/REST/Attendees.php

Fires when an administrator edits an attendee’s profile fields (display name, email or quantity) through PATCH /attendees/{id}. The payload is a before and after diff limited to the fields that really changed.

Signature

do_action( 'happenboard_attendee_updated', int $attendee_id, array<string,array{from:mixed,to:mixed}> $changes )

Parameters

$attendee_id int
Primary key of the attendee row.
$changes array<string,array{from:mixed,to:mixed}>
Diff keyed by database column, each entry holding from and to.

Example

add_action(
    'happenboard_attendee_updated',
    function ( int $attendee_id, array $changes ): void {
        if ( ! isset( $changes['email'] ) ) {
            return;
        }

        my_plugin_sync_contact(
            (string) $changes['email']['from'],
            (string) $changes['email']['to']
        );
    },
    10,
    2
);

Notes

  • Two arguments are passed, so register with 10, 2.
  • The keys of $changes are database column names: display_name, email and quantity. They are not the camelCase names the REST request uses.
  • Only fields whose value actually changed appear. A request that resends identical values still fires the action, with an empty array. Return early on an empty diff.
  • Status is not part of this diff. A single request that edits the profile and flips the status fires this hook and happenboard_attendee_status_changed, profile first.
  • The call has no docblock. HappenBoard Pro reads the diff with two arguments to render the before and after in the attendee timeline.

happenboard_pro_attendee_checked_in

Action Pro Since 1.0 src/Service/Checkin/Repository.php

Fires after a check-in scan has been recorded at the door. Use it to print a badge, notify a team channel, or refresh a live roster.

Signature

do_action( 'happenboard_pro_attendee_checked_in', int $checkin_id, int $attendee_id, int $event_id )

Parameters

$checkin_id int
Primary key of the check-in row that was just inserted.
$attendee_id int
The attendee who was scanned.
$event_id int
The event the attendee was scanned into.

Example

add_action(
    'happenboard_pro_attendee_checked_in',
    function ( int $checkin_id, int $attendee_id, int $event_id ): void {
        error_log(
            sprintf(
                'Scan %d: attendee %d entered event %d.',
                $checkin_id,
                $attendee_id,
                $event_id
            )
        );
    },
    10,
    3
);

Notes

  • Three arguments are passed, so register with 10, 3.
  • The first argument is the check-in row id, not the attendee id. It is easy to use the wrong one because both are integers.
  • It fires on every scan, re-scans included. A second scan of the same ticket inserts a second row and fires again with a new $checkin_id. Deduplicate yourself if you only want the first entry.
  • It only fires after a successful insert. When the insert fails the repository returns 0 and nothing is dispatched.
  • HappenBoard Pro already listens: the attendee timeline at 10, the MailerPress bridge and the webhook dispatcher at 20, Slack at 30. The MailerPress bridge re-emits it as happenboard_mp_checked_in.

happenboard_rest_attendee_response

Filter Free Since 1.0 src/REST/Attendees.php

Filters one serialized attendee row on its way out of the REST API. This is how you add your own columns to the admin attendee list.

Signature

apply_filters( 'happenboard_rest_attendee_response', array<string,mixed> $serialized, array<string,mixed> $row )

Parameters

$serialized array<string,mixed>
The camelCase payload sent to the admin interface: id, eventId, email, displayName, status, quantity, customFields and the timestamps.
$row array<string,mixed>
The raw database row, with snake_case columns and custom_fields still JSON encoded.

Return value

array<string,mixed> The serialized attendee, with your own keys added. Always return an array.

Example

add_filter(
    'happenboard_rest_attendee_response',
    function ( array $serialized, array $row ): array {
        $serialized['myPluginTier'] = (string) get_post_meta(
            (int) ( $row['event_id'] ?? 0 ),
            '_my_plugin_tier',
            true
        );

        return $serialized;
    },
    10,
    2
);

Notes

  • Two arguments are passed, so register with 10, 2. The raw row is there so you do not have to query the table again.
  • The result is cast with (array). Returning a string, an object or null produces a broken row instead of an error, so always return the array you were handed.
  • The same serializer feeds the list endpoint and the CSV or JSON export, so any column you add here also lands in exported files.
  • It runs once per row. A query inside your callback becomes one query per attendee on a list of 200. HappenBoard Pro already pays that cost here to add checkinCount and lastCheckinUtc at priority 10.
  • Add keys, do not rebuild the array. Replacing $serialized wholesale drops both the core fields and anything an earlier callback added.

happenboard_rsvp_cancelled

Action Free and Pro Since 1.0 src/REST/Rsvp.php

Fires when an attendee’s sign-up is cancelled and their seat released, whether the visitor cancelled it themselves or a payment was refunded. Use it to free up your own resources or trigger a win-back message.

Signature

do_action( 'happenboard_rsvp_cancelled', int $attendee_id )

Parameters

$attendee_id int
Primary key of the attendee row that was cancelled.

Example

add_action(
    'happenboard_rsvp_cancelled',
    function ( int $attendee_id ): void {
        $repository = new \HappenBoard\Service\Attendees\Repository();
        $attendee   = $repository->find_by_id( $attendee_id );

        if ( null !== $attendee ) {
            error_log( 'Seat released for ' . (string) $attendee['email'] );
        }
    }
);

Notes

  • A single argument is passed. The row is cancelled, not deleted, so you can still read it from the attendees repository with its status set to cancelled.
  • Four different places fire it. In HappenBoard: the REST cancel route in src/REST/Rsvp.php and the public cancel page in src/Service/Frontend/CancelPage.php. In HappenBoard Pro: the Stripe refund handler in src/Service/Stripe/WebhookHandler.php and the WooCommerce order reversal in src/Service/WooCommerce/OrderBridge.php.
  • Because of the Pro paths, your callback can run inside an incoming payment webhook with no logged-in user and no front-end context. Do not call anything that assumes a current user or a rendered page.
  • A refund or a reversed order fires it once per attendee row created by that order, so a single refund can fire it several times in a row.
  • An administrator setting the status to cancelled from the attendees list does not fire this hook. That path fires happenboard_attendee_status_changed instead.

happenboard_rsvp_client_ip

Filter Free and Pro Since 1.0 src/REST/Rsvp.php

Filters the client IP the sign-up rate limiter keys on. HappenBoard reads REMOTE_ADDR and deliberately ignores X-Forwarded-For, so behind a reverse proxy or a CDN every visitor shows up as the proxy address and five sign-ups per minute lock out the whole site.

Signature

apply_filters( 'happenboard_rsvp_client_ip', string $ip )

Parameters

$ip string
REMOTE_ADDR after FILTER_VALIDATE_IP, or an empty string when it could not be parsed.

Return value

string A validated IP address used as the rate-limiter key. Return $ip unchanged when you cannot do better.

Example

add_filter(
    'happenboard_rsvp_client_ip',
    function ( string $ip ): string {
        // Only read this header if the site really is behind Cloudflare.
        $forwarded = isset( $_SERVER['HTTP_CF_CONNECTING_IP'] )
            ? sanitize_text_field( wp_unslash( (string) $_SERVER['HTTP_CF_CONNECTING_IP'] ) )
            : '';

        return filter_var( $forwarded, FILTER_VALIDATE_IP ) ? $forwarded : $ip;
    }
);

Notes

  • The correct fix is to read the one header your own proxy sets, validate it with filter_var( $value, FILTER_VALIDATE_IP ), and fall back to $ip when it does not parse. Never return a header value straight through: a header a visitor controls is a throttle a visitor can rotate away.
  • If you use X-Forwarded-For, take the hop your own proxy appended, not the first entry of the list, which the client can write freely. When you do not know for sure what sits in front of WordPress, leave this filter alone.
  • One argument only, so a plain add_filter() is enough.
  • The same filter is applied in HappenBoard Pro’s front-end event submission endpoint (src/REST/EventSubmissions.php), so a single callback changes both throttles: 5 sign-ups per minute and 3 event submissions per 10 minutes. Appointment bookings have their own copy, happenboard_booking_client_ip in src/REST/Bookings.php: filtering one does not filter the other.
  • Returning an empty string switches the per-IP throttle off entirely on both endpoints, since both skip it when the address is empty. The per-email throttle, the honeypot and the minimum fill time still apply. The value is also passed to Turnstile and Akismet as the visitor IP, so a wrong value degrades spam scoring.

happenboard_rsvp_custom_fields

Filter Free Since 1.0 src/REST/Rsvp.php

Validates the extra answers submitted with a sign-up and decides what gets stored on the attendee row. This is the companion of happenboard_rsvp_form_fields: that one draws the input, this one keeps the value.

Signature

apply_filters( 'happenboard_rsvp_custom_fields', array<string,mixed> $fields, array<string,string|array<int,string>> $raw, int $event_id )

Parameters

$fields array<string,mixed>
The answers accumulated so far. An empty array by default.
$raw array<string,string|array<int,string>>
What the visitor submitted, keyed by field key. Keys are already through sanitize_key() and values through sanitize_text_field().
$event_id int
The event being signed up for.

Return value

array<string,mixed>|WP_Error The answers to store on the attendee row, or a WP_Error to reject the sign-up.

Example

add_filter(
    'happenboard_rsvp_custom_fields',
    function ( $fields, array $raw, int $event_id ) {
        if ( ! is_array( $fields ) ) {
            $fields = array();
        }

        $diet = isset( $raw['diet'] ) ? sanitize_text_field( (string) $raw['diet'] ) : '';

        if ( '' === $diet ) {
            return new WP_Error(
                'my_plugin_diet_required',
                __( 'Please tell us about your dietary requirements.', 'my-plugin' ),
                array( 'status' => 400 )
            );
        }

        $fields['diet'] = $diet;

        return $fields;
    },
    20,
    3
);

Notes

  • Three arguments are passed, so register with 10, 3 at least. Use priority 20 when HappenBoard Pro may be active: its own validator runs at 10.
  • Merge into the incoming array rather than replacing it. When Pro has attendee fields defined for that event, its validator returns only the values built from its own definitions, so anything a lower priority added is dropped. Running after it and merging is the safe order.
  • The keys in $raw are input names with the happenboard_cf_ prefix removed by the form script, which also skips disabled inputs. An input not named happenboard_cf_<key> never reaches this filter, no matter how you rendered it.
  • Return a WP_Error to refuse the sign-up: it is handed straight back by the REST endpoint and its message is shown under the form. Anything that is neither an array nor a WP_Error is turned into an empty array and every answer is dropped silently.
  • What you return is JSON encoded into the attendee’s custom_fields column, so stick to scalars and arrays of strings. An empty array is stored as NULL. Values arriving pre-sanitized is not the same as validated: check ranges, formats and allowed options yourself.

happenboard_rsvp_form_fields

Filter Free Since 1.0 src/Block/RsvpForm/Block.php

Injects extra HTML into the sign-up form, after the email field and before the quantity selector. This is the hook to use when you want your own inputs on the form.

Signature

apply_filters( 'happenboard_rsvp_form_fields', string $html, int $post_id )

Parameters

$html string
The HTML to inject. Empty by default.
$post_id int
The ID of the event being registered for.

Return value

string HTML printed as is. Escaping is your responsibility.

Example

add_filter(
    'happenboard_rsvp_form_fields',
    function ( string $html, int $post_id ): string {
        $html .= sprintf(
            '<label class="wp-block-happenboard-rsvp-form__field">
                <span>%s</span>
                <input type="text" name="happenboard_cf_diet" maxlength="190">
            </label>',
            esc_html__( 'Dietary requirements', 'my-plugin' )
        );
        return $html;
    },
    10,
    2
);

Notes

  • The filter passes two arguments, so pass 10, 2 to add_filter(). Leaving out the 2 means $post_id never reaches your callback.
  • Append to $html rather than replacing it. HappenBoard Pro renders its own custom fields through this same filter, and returning a fresh string would silently remove them.
  • The value is printed without escaping, so escape everything you build yourself.
  • Rendering an input is not enough to store what the visitor types. The submitted value is discarded unless you also validate it through happenboard_rsvp_custom_fields, which fires in src/REST/Rsvp.php when the form is posted. Naming your inputs happenboard_cf_<key> follows the convention HappenBoard Pro uses.

happenboard_rsvp_submitted

Action Free and Pro Since 1.0 src/REST/Rsvp.php

Fires right after an attendee row has been created, on free sign-ups as well as on paid orders. This is the entry point for everything that has to happen when someone registers.

Signature

do_action( 'happenboard_rsvp_submitted', int $attendee_id, array{event_id:int,email:string,display_name:string,quantity:int,cancel_token:string} $data )

Parameters

$attendee_id int
Primary key of the attendee row that was just inserted.
$data array<string,mixed>
The submission payload: event_id, email, display_name, quantity and cancel_token.

Example

add_action(
    'happenboard_rsvp_submitted',
    function ( int $attendee_id, array $data ): void {
        error_log(
            sprintf(
                'Attendee %d signed up for event %d with %d seat(s).',
                $attendee_id,
                (int) ( $data['event_id'] ?? 0 ),
                (int) ( $data['quantity'] ?? 1 )
            )
        );
    },
    10,
    2
);

Notes

  • Two arguments are passed, so register with 10, 2.
  • Three places fire it with the same five keys: the sign-up endpoint in HappenBoard (src/REST/Rsvp.php), and in HappenBoard Pro the Stripe checkout webhook (src/Service/Stripe/WebhookHandler.php) and the WooCommerce order bridge (src/Service/WooCommerce/OrderBridge.php). Paid paths fire once per ticket line, so a single order can fire it several times.
  • $data is the submission payload, not the database row. It carries no status, no custom_fields and no timestamps. Read the row through the attendees repository when you need those.
  • cancel_token is the secret behind the one-click cancel link. It is deliberately kept out of the REST response. Do not log it, do not put it in a URL you expose, do not forward it to a third party.
  • On the two paid paths the token is read back with a query for the newest row of the event rather than by primary key, so under concurrent orders it can be another attendee’s token. Read the row by $attendee_id if the token matters to you. The free confirmation email is sent on this hook at priority 10; HappenBoard Pro listens at 10, 20, 30 and 40.