iCal Calendar Feed
This document describes the iCal calendar subscription feature that allows users to subscribe to birthdays in external calendar applications.
Overview
Section titled “Overview”Rondo Club generates iCal feeds for users containing birthdays from person records. There are two types of feeds:
- Personal Feed - Contains birthdays for all people accessible to the user
- Workspace Feed - Contains birthdays for contacts shared with a workspace
Both feed types use a secret token for authentication, allowing calendar apps to fetch updates without requiring login credentials.
Features
Section titled “Features”- Token-based authentication - No password needed for calendar apps
- User-specific feeds - Only shows birthdays for people you can access
- Workspace feeds - Subscribe to birthdays for all workspace contacts
- Recurring events - Birthdays repeat annually
- Real-time updates - Calendar apps refresh periodically to get new dates
- Universal compatibility - Works with Apple Calendar, Google Calendar, Outlook, and any iCal-compatible app
Feed URL Formats
Section titled “Feed URL Formats”Personal Feed
Section titled “Personal Feed”https://your-site.com/calendar/{token}.icsWorkspace Feed
Section titled “Workspace Feed”https://your-site.com/workspace/{workspace_id}/calendar/{token}.icsThe token is a 64-character hexadecimal string (32 bytes) stored in user meta. The same token is used for both personal and workspace feeds.
webcal:// Protocol:
For one-click subscription, use the webcal:// protocol:
webcal://your-site.com/calendar/{token}.icswebcal://your-site.com/workspace/{workspace_id}/calendar/{token}.icsImplementation
Section titled “Implementation”Class: Rondo\Calendar\ICalFeed
Section titled “Class: Rondo\Calendar\ICalFeed”Located in includes/class-ical-feed.php.
Key Components:
| Component | Purpose |
|---|---|
TOKEN_META_KEY | User meta key: rondo_ical_token |
TOKEN_LENGTH | 32 bytes (64 hex characters) |
| Personal Rewrite Rule | ^calendar/([a-f0-9]+)\.ics$ |
| Workspace Rewrite Rule | ^workspace/([0-9]+)/calendar/([a-f0-9]+)\.ics$ |
| REST Endpoints | URL retrieval and token regeneration |
Token Management
Section titled “Token Management”Token Generation:
bin2hex(random_bytes(32))Token Storage:
update_user_meta($user_id, 'rondo_ical_token', $token);Token Lookup:
SELECT user_id FROM wp_usermetaWHERE meta_key = 'rondo_ical_token' AND meta_value = '{token}'REST API Endpoints
Section titled “REST API Endpoints”Get Calendar URL
Section titled “Get Calendar URL”GET /rondo/v1/user/ical-url
Returns the current user’s iCal feed URL.
Response:
{ "url": "https://your-site.com/calendar/abc123...def.ics", "webcal_url": "webcal://your-site.com/calendar/abc123...def.ics", "token": "abc123...def"}The token field can be used to construct workspace calendar URLs on the frontend.
Regenerate Token
Section titled “Regenerate Token”POST /rondo/v1/user/regenerate-ical-token
Creates a new token, invalidating the old URL.
Response:
{ "success": true, "url": "https://your-site.com/calendar/new456...xyz.ics", "webcal_url": "webcal://your-site.com/calendar/new456...xyz.ics", "message": "Your calendar URL has been regenerated. Update any calendar subscriptions with the new URL."}Important: Regenerating the token invalidates all existing calendar subscriptions. Users must update their calendar apps with the new URL.
iCal Format
Section titled “iCal Format”Calendar Structure
Section titled “Calendar Structure”BEGIN:VCALENDARVERSION:2.0PRODID:-//Rondo Club//Site Name//ENCALSCALE:GREGORIANMETHOD:PUBLISHX-WR-CALNAME:Site Name - BirthdaysX-WR-TIMEZONE:UTC
[VEVENT entries...]
END:VCALENDAREvent Structure
Section titled “Event Structure”BEGIN:VEVENTDTSTAMP:20250104T120000ZDTSTART;VALUE=DATE:19850615DTEND;VALUE=DATE:19850616SUMMARY:John Doe's BirthdayDESCRIPTION:BirthdayURL:https://your-site.com/people/456RRULE:FREQ=YEARLYEND:VEVENTField Mapping:
| iCal Field | Source |
|---|---|
UID | birthday-{person_id}@{domain} |
DTSTAMP | Person modified date (GMT) |
DTSTART | birthdate ACF field on person |
DTEND | Birthdate + 1 day |
SUMMARY | {Person Name}'s Birthday |
DESCRIPTION | Birthday |
URL | Link to person |
RRULE | FREQ=YEARLY (always recurring) |
All-Day Events
Section titled “All-Day Events”Dates are rendered as all-day events using VALUE=DATE format:
DTSTART;VALUE=DATE:20250615DTEND;VALUE=DATE:20250616Recurring Events
Section titled “Recurring Events”All birthday events include:
RRULE:FREQ=YEARLYThis makes birthdays repeat annually on the same day.
Subscribing to the Feed
Section titled “Subscribing to the Feed”Apple Calendar (macOS/iOS)
Section titled “Apple Calendar (macOS/iOS)”- Open Calendar app
- File → New Calendar Subscription (or tap Add Subscription)
- Paste the feed URL
- Adjust refresh interval (recommended: every day)
- Click Subscribe
Google Calendar
Section titled “Google Calendar”- Open Google Calendar (web)
- Click + next to “Other calendars”
- Select “From URL”
- Paste the feed URL
- Click “Add calendar”
Note: Google Calendar may take several hours to initially sync.
Microsoft Outlook
Section titled “Microsoft Outlook”- Open Outlook
- File → Account Settings → Internet Calendars
- Click “New”
- Paste the feed URL
- Click OK
Other Apps
Section titled “Other Apps”Any app supporting iCal/ICS format can subscribe using the feed URL.
Security
Section titled “Security”Token Security
Section titled “Token Security”- Random generation - Uses
random_bytes()for cryptographic randomness - Sufficient length - 32 bytes provides 256 bits of entropy
- Direct DB lookup - No timing attacks via user enumeration
Best Practices
Section titled “Best Practices”- Don’t share URLs - Treat the calendar URL as a password
- Regenerate if compromised - Use the regenerate endpoint
- HTTPS required - Feed URLs should always use HTTPS
Access Control
Section titled “Access Control”The feed respects the same access control as the web interface:
- Only shows birthdays for people the user can access
- Administrators see all birthdays
Workspace Feed Security
Section titled “Workspace Feed Security”Workspace feeds include additional security checks:
- Token validation - User’s iCal token must be valid
- Workspace existence - Workspace must exist and be published
- Membership verification - User must be a member of the workspace (any role)
If any check fails, the request is rejected with an appropriate HTTP error:
403 Forbidden- Invalid token or not a workspace member404 Not Found- Workspace does not exist
Workspace Feeds
Section titled “Workspace Feeds”How It Works
Section titled “How It Works”Workspace feeds aggregate birthdays from all contacts shared with a workspace:
- Find all people (
personposts) tagged withworkspace-{id}in theworkspace_accesstaxonomy - Query all people with a
birthdatefield set - Generate iCal events for all birthdays found
Accessing Workspace Feeds
Section titled “Accessing Workspace Feeds”The workspace calendar URL is shown on the WorkspaceDetail page in the frontend. Users can copy the URL and subscribe in their calendar app.
URL Format:
https://your-site.com/workspace/{workspace_id}/calendar/{token}.icsCalendar Name
Section titled “Calendar Name”Workspace calendars are named “Rondo Club - {Workspace Name}” to help users identify them in their calendar app.
Technical Details
Section titled “Technical Details”Rewrite Rules
Section titled “Rewrite Rules”The feed uses WordPress rewrite rules:
Personal Feed:
add_rewrite_rule( '^calendar/([a-f0-9]+)\.ics$', 'index.php?rondo_ical_feed=1&rondo_ical_token=$matches[1]', 'top');Workspace Feed:
add_rewrite_rule( '^workspace/([0-9]+)/calendar/([a-f0-9]+)\.ics$', 'index.php?rondo_workspace_ical=1&rondo_workspace_id=$matches[1]&rondo_ical_token=$matches[2]', 'top');Query Variables:
rondo_ical_feed- Triggers personal feed handlerrondo_workspace_ical- Triggers workspace feed handlerrondo_ical_token- User’s authentication tokenrondo_workspace_id- Workspace post ID
Note: After theme activation, rewrite rules are flushed to register these rules.
Headers
Section titled “Headers”header('Content-Type: text/calendar; charset=utf-8');header('Content-Disposition: attachment; filename="rondo.ics"');header('Cache-Control: no-cache, must-revalidate');header('Pragma: no-cache');Text Escaping
Section titled “Text Escaping”Special characters are escaped per iCal spec:
| Character | Escaped |
|---|---|
\ | \\ |
, | \, |
; | \; |
| newline | \n |
Troubleshooting
Section titled “Troubleshooting”Calendar not updating
Section titled “Calendar not updating”- Most calendar apps refresh every 24 hours
- Try removing and re-adding the subscription
- Check that the feed URL is accessible
404 Error
Section titled “404 Error”- Rewrite rules may need flushing
- Visit Settings → Permalinks in WordPress admin (saves/flushes rules)
Events not appearing
Section titled “Events not appearing”- Verify people have the
birthdatefield set - Check access control - user may not have access to those people
Related Documentation
Section titled “Related Documentation”- Data Model - Person post type with birthdate field
- Reminders - Email reminder system
- Access Control - How person visibility works