eventbrite-for-the-events-c.../includes/class-eb4tec-webhook.php
Laurence Horrocks-Barlow f3bc795d9a Initial release of Eventbrite for The Events Calendar (v1.0.0)
Bidirectional sync between Eventbrite and The Events Calendar, with
WooCommerce ticket purchasing that bypasses Eventbrite's processing
fees by registering buyers as free attendees via API. Includes venue/
organizer sync, QR code ticket generation, attendee management with
CSV export, scheduled sync via WP-Cron, and real-time Eventbrite
webhooks.
2026-05-17 08:48:04 +01:00

149 lines
3.9 KiB
PHP

<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
final class EB4TEC_Webhook {
public function __construct(
private readonly EB4TEC_API_Client $api,
private readonly EB4TEC_Event_Sync $event_sync,
) {
add_filter( 'query_vars', [ $this, 'add_query_var' ] );
add_action( 'template_redirect', [ $this, 'handle_webhook' ] );
}
public function add_query_var( array $vars ): array {
$vars[] = 'eb4tec_webhook';
return $vars;
}
public function handle_webhook(): void {
if ( ! get_query_var( 'eb4tec_webhook' ) ) {
return;
}
$payload = file_get_contents( 'php://input' );
$signature = $_SERVER['HTTP_X_EVENTBRITE_SIGNATURE'] ?? '';
if ( ! $this->verify_signature( $payload, $signature ) ) {
wp_die( 'Forbidden', 'Forbidden', [ 'response' => 403 ] );
}
$data = json_decode( $payload, true );
if ( ! is_array( $data ) ) {
wp_send_json( [ 'status' => 'error', 'message' => 'Invalid JSON' ], 400 );
}
$action = $data['config']['action'] ?? '';
$api_url = $data['api_url'] ?? '';
// Extract EB event ID from the API URL (e.g. .../events/123456789/).
$eb_event_id = $this->extract_event_id_from_url( $api_url );
switch ( $action ) {
case 'event.published':
case 'event.updated':
if ( $eb_event_id ) {
$this->event_sync->pull_event( $eb_event_id );
}
break;
case 'event.unpublished':
if ( $eb_event_id ) {
$this->handle_event_unpublished( $eb_event_id );
}
break;
case 'attendee.updated':
$this->handle_attendee_updated( $data );
break;
default:
// Unknown action — acknowledge without processing.
break;
}
wp_send_json( [ 'status' => 'ok' ], 200 );
}
private function verify_signature( string $payload, string $signature ): bool {
$secret = (string) get_option( 'eb4tec_webhook_secret', '' );
// If no secret is configured, skip verification (allows initial setup).
if ( empty( $secret ) ) {
return true;
}
if ( empty( $signature ) ) {
return false;
}
$expected = hash_hmac( 'sha256', $payload, $secret );
return hash_equals( $expected, $signature );
}
private function handle_event_unpublished( string $eb_event_id ): void {
$query = new WP_Query( [
'post_type' => 'tribe_events',
'post_status' => [ 'publish', 'draft' ],
'posts_per_page' => 1,
'fields' => 'ids',
'meta_query' => [ [
'key' => '_eb4tec_event_id',
'value' => $eb_event_id,
] ],
] );
if ( ! empty( $query->posts ) ) {
$post_id = $query->posts[0];
wp_update_post( [
'ID' => $post_id,
'post_status' => 'draft',
] );
update_post_meta( $post_id, '_eb4tec_eb_status', 'unpublished' );
}
}
private function handle_attendee_updated( array $data ): void {
// Extract attendee ID from the API URL if present.
$api_url = $data['api_url'] ?? '';
$attendee_id = $this->extract_id_from_url( $api_url, 'attendees' );
if ( ! $attendee_id ) {
return;
}
// Find any WC order that has this attendee and update its note.
global $wpdb;
$order_ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_eb4tec_attendee_ids' AND meta_value LIKE %s",
'%' . $wpdb->esc_like( $attendee_id ) . '%'
)
);
foreach ( $order_ids as $order_id ) {
$order = wc_get_order( $order_id );
if ( $order ) {
$order->add_order_note(
sprintf( __( 'EB4TEC: Eventbrite attendee %s was updated.', 'eb4tec' ), esc_html( $attendee_id ) ),
false,
false
);
}
}
}
private function extract_event_id_from_url( string $url ): string {
return $this->extract_id_from_url( $url, 'events' );
}
private function extract_id_from_url( string $url, string $resource ): string {
// URLs look like: https://www.eventbriteapi.com/v3/events/123456789/
if ( preg_match( '#/' . preg_quote( $resource, '#' ) . '/(\d+)/#', $url, $matches ) ) {
return $matches[1];
}
return '';
}
}