Integration with Bevy event management platform to automatically create and sync event topics in Discourse.
- Receives webhooks from Bevy when events are created or updated
- Automatically creates Discourse topics for published events (Draft events are ignored)
- Updates existing topics when Bevy events are modified
- Event deduplication using timestamps to prevent stale updates
- Only processes events with "Published" status
- Integrates with discourse-post-event plugin to create calendar events
- Syncs attendee registrations from Bevy to discourse-post-event
- Maps Bevy attendee statuses to Discourse invitee statuses:
registered→goingdeleted→not_going
- Matches attendees by email address to Discourse users
- Automatically creates/updates invitee records for discourse-post-event
- Attempts to match event creators by email and posts as that user
- Falls back to system user if email doesn't match any Discourse user
- Supports custom user identification via
published_byfield in webhook payload
- Configurable tag extraction using JMESPath expressions
- Extract tags from any field in the Bevy event payload
- Automatically applies tags to created/updated topics
- Creates rich event topics with:
- Event title as the topic title
- Event description and short description
- Event image (if available)
- Event dates (start and end)
- Location information (venue name and address)
- Event type and chapter information
- discourse-post-event integration with proper date/time formatting
- Link back to the Bevy event page
- API key authentication via
X-BEVY-SECRETheader - Validates all incoming webhook requests
- Configurable webhook API key
- Go to Admin → Settings → Plugins → bevy_plugin
- Enable the plugin: Check
bevy_plugin_enabled - Set webhook API key: Enter a secure random string in
bevy_webhook_api_key - Configure category: Select the category for event topics in
bevy_webhook_category_id
- Tag Rules (
bevy_events_tag_rules): JMESPath expressions to extract tags from event data- Example:
chapter.cityto tag events with their chapter city - Multiple rules can be defined (see Tag Extraction section below)
- Example:
-
In your Bevy admin panel, go to Settings → Webhooks
-
Create a new webhook with the following settings:
- URL:
https://your-discourse-url.com/bevy/webhooks - Events: Select both:
- "Event updates" (for event creation/updates)
- "Attendee updates" (for registration syncing)
- Authentication: Add a custom header:
- Header name:
X-BEVY-SECRET - Header value: The API key you configured in plugin settings
- Header name:
- Extra Data Fields: Request Bevy customer service to include the
published_byfield to enable user matching by email when creating bevy events
- URL:
-
Test the webhook to ensure it's working correctly
When Bevy sends a webhook for a published event:
- Authentication: Validates the
X-BEVY-SECRETheader matches your configured API key - Deduplication: Checks if event exists and compares
updated_tsto prevent processing stale webhooks - User Lookup: Attempts to find a Discourse user matching the
published_by.email(if the field is included) - Topic Creation/Update:
- New events: Creates a new topic with event details
- Existing events: Updates the existing topic (title, content, tags)
- Post Event Integration: Adds discourse-post-event markup with dates and timezone
- Tag Application: Applies tags extracted via JMESPath rules
- Database Tracking: Records the event with its
bevy_event_idandupdated_tsfor future updates
When Bevy sends a webhook for attendee registrations:
- Authentication: Validates the webhook signature
- Event Lookup: Finds the corresponding Discourse topic and post event
- User Matching: Matches attendee emails to Discourse user accounts
- Status Mapping: Converts Bevy statuses to discourse-post-event invitee statuses
- Bulk Sync: Uses
upsert_allto efficiently sync all attendees for the event
The plugin tracks events using two fields:
bevy_event_id: The unique event ID from Bevybevy_updated_ts: The timestamp of the last update from Bevy
When a webhook is received:
- If the event doesn't exist, it's created
- If the event exists and the new
updated_tsis newer, it's updated - If the event exists and the new
updated_tsis older or equal, the webhook is ignored (returns 200 OK with "Event already up to date" message)
This prevents race conditions and ensures only the latest event data is used.
The plugin supports extracting tags from Bevy event data using JMESPath expressions. Configure rules in the bevy_events_tag_rules setting.
The setting uses a pipe-separated format where each rule consists of a tag name and a JMESPath expression separated by a comma:
TagName1,expression1|TagName2,expression2|TagName3,expression3
Structure:
- Multiple rules separated by
|(pipe) - Each rule has two parts separated by
,(comma):- Tag name: The literal tag that will be added to the topic (e.g., "dev", "production", "virtual")
- JMESPath expression: A boolean test or value extraction - if the result is truthy, the tag name is added
- Note: Whitespace around separators and values is automatically stripped, so spacing is flexible
How it works:
The expression is evaluated against the event data. If it returns a truthy value (not false, null, or empty), the tag name is added to the topic.
Add "test" tag for test events:
test,contains(chapter.chapter_location, 'Test')
Result: If chapter location contains "Test", adds tag "test"
Add environment tags based on location:
staging,contains(chapter.chapter_location, 'Staging')|production,contains(chapter.chapter_location, 'Production')
Result: Adds "staging" tag for staging chapters, "production" tag for production chapters
Add "virtual" tag for virtual events:
virtual,event_type_title == 'Virtual Event type'
Result: If event type is "Virtual Event type", adds tag "virtual"
Add country tags:
usa,chapter.country == 'US'|canada,chapter.country == 'CA'|uk,chapter.country == 'GB'
Result: Adds country tag based on chapter location
Add tag if field exists and has a value:
has-venue,venue_name
Result: If venue_name is not empty/null, adds tag "has-venue"
Complex: Check nested team data:
has-leader,chapter.chapter_team[0].title
Result: If the first team member has a title, adds tag "has-leader"
- All tags are automatically lowercased and sanitized
- Duplicate tags are removed
- Empty/null values are filtered out
- Tags are created in Discourse if they don't exist
- Existing topics get their tags updated when the event is updated
- discourse-calendar: For calendar event integration and attendee management
- Webhook support enabled in your Bevy account
- Access to Bevy admin panel to configure webhooks
- Events must include the
published_byfield for user matching
- Check that the plugin is enabled in settings
- Verify the webhook URL is correct:
https://your-site.com/bevy/webhooks - Check your Discourse logs for webhook requests:
Admin → Logs → Staff Action Logs - Verify ngrok or your proxy is properly forwarding requests (if testing locally)
- Verify the
X-BEVY-SECRETheader value matches yourbevy_webhook_api_keysetting exactly - Check for extra whitespace or encoding issues in the API key
- Review logs for "Unauthorized" or "Webhook not configured" errors
- Verify the event status is "Published" (Draft events are skipped)
- Check that
bevy_webhook_category_idis set to a valid category - Ensure discourse-post-event plugin is installed and enabled
- Review Rails logs for specific error messages
- Verify the discourse-post-event plugin is installed
- Check that the event has been created (attendee sync requires an existing event)
- Ensure attendee emails match Discourse user emails
- Review logs for "No post found for event" warnings
- Check that the
updated_tsin the webhook is newer than the stored timestamp - The plugin intentionally ignores webhooks with older timestamps to prevent race conditions
- If an event isn't updating, it may be receiving a stale webhook - check Bevy's webhook delivery logs
From your discourse directory run:
LOAD_Plugins=1 bundle exec rspec plugins/discourse-bevy-plugin/specThe plugin creates one custom model:
BevyEvent
bevy_event_id(integer): The Bevy event IDpost_id(integer): The Discourse post ID for the event topicbevy_updated_ts(datetime): The last update timestamp from Bevy
See Bevy's webhook documentation for complete payload structures. The plugin processes:
Event webhook:
[{
"type": "event",
"data": [{
"id": 123,
"title": "Event Title",
"status": "Published",
"updated_ts": "2024-01-20T10:00:00Z",
...
}]
}]Attendee webhook:
[{
"type": "attendee",
"data": [{
"event_id": 123,
"email": "user@example.com",
"status": "registered",
...
}]
}]MIT License - See LICENSE file for details
For issues and feature requests, please use the GitHub issue tracker.