Skip to content

iCal Calendar Feed

This document describes the iCal calendar subscription feature that allows users to subscribe to birthdays in external calendar applications.

Rondo Club generates iCal feeds for users containing birthdays from person records. There are two types of feeds:

  1. Personal Feed - Contains birthdays for all people accessible to the user
  2. 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.

  • 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
https://your-site.com/calendar/{token}.ics
https://your-site.com/workspace/{workspace_id}/calendar/{token}.ics

The 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}.ics
webcal://your-site.com/workspace/{workspace_id}/calendar/{token}.ics

Located in includes/class-ical-feed.php.

Key Components:

ComponentPurpose
TOKEN_META_KEYUser meta key: rondo_ical_token
TOKEN_LENGTH32 bytes (64 hex characters)
Personal Rewrite Rule^calendar/([a-f0-9]+)\.ics$
Workspace Rewrite Rule^workspace/([0-9]+)/calendar/([a-f0-9]+)\.ics$
REST EndpointsURL retrieval and token regeneration

Token Generation:

bin2hex(random_bytes(32))

Token Storage:

update_user_meta($user_id, 'rondo_ical_token', $token);

Token Lookup:

SELECT user_id FROM wp_usermeta
WHERE meta_key = 'rondo_ical_token' AND meta_value = '{token}'

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.

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.

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Rondo Club//Site Name//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:Site Name - Birthdays
X-WR-TIMEZONE:UTC
[VEVENT entries...]
END:VCALENDAR
BEGIN:VEVENT
UID:birthday-456@your-site.com
DTSTAMP:20250104T120000Z
DTSTART;VALUE=DATE:19850615
DTEND;VALUE=DATE:19850616
SUMMARY:John Doe's Birthday
DESCRIPTION:Birthday
URL:https://your-site.com/people/456
RRULE:FREQ=YEARLY
END:VEVENT

Field Mapping:

iCal FieldSource
UIDbirthday-{person_id}@{domain}
DTSTAMPPerson modified date (GMT)
DTSTARTbirthdate ACF field on person
DTENDBirthdate + 1 day
SUMMARY{Person Name}'s Birthday
DESCRIPTIONBirthday
URLLink to person
RRULEFREQ=YEARLY (always recurring)

Dates are rendered as all-day events using VALUE=DATE format:

DTSTART;VALUE=DATE:20250615
DTEND;VALUE=DATE:20250616

All birthday events include:

RRULE:FREQ=YEARLY

This makes birthdays repeat annually on the same day.

  1. Open Calendar app
  2. File → New Calendar Subscription (or tap Add Subscription)
  3. Paste the feed URL
  4. Adjust refresh interval (recommended: every day)
  5. Click Subscribe
  1. Open Google Calendar (web)
  2. Click + next to “Other calendars”
  3. Select “From URL”
  4. Paste the feed URL
  5. Click “Add calendar”

Note: Google Calendar may take several hours to initially sync.

  1. Open Outlook
  2. File → Account Settings → Internet Calendars
  3. Click “New”
  4. Paste the feed URL
  5. Click OK

Any app supporting iCal/ICS format can subscribe using the feed URL.

  • 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
  1. Don’t share URLs - Treat the calendar URL as a password
  2. Regenerate if compromised - Use the regenerate endpoint
  3. HTTPS required - Feed URLs should always use HTTPS

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 feeds include additional security checks:

  1. Token validation - User’s iCal token must be valid
  2. Workspace existence - Workspace must exist and be published
  3. 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 member
  • 404 Not Found - Workspace does not exist

Workspace feeds aggregate birthdays from all contacts shared with a workspace:

  1. Find all people (person posts) tagged with workspace-{id} in the workspace_access taxonomy
  2. Query all people with a birthdate field set
  3. Generate iCal events for all birthdays found

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}.ics

Workspace calendars are named “Rondo Club - {Workspace Name}” to help users identify them in their calendar app.

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 handler
  • rondo_workspace_ical - Triggers workspace feed handler
  • rondo_ical_token - User’s authentication token
  • rondo_workspace_id - Workspace post ID

Note: After theme activation, rewrite rules are flushed to register these rules.

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');

Special characters are escaped per iCal spec:

CharacterEscaped
\\\
,\,
;\;
newline\n
  • Most calendar apps refresh every 24 hours
  • Try removing and re-adding the subscription
  • Check that the feed URL is accessible
  • Rewrite rules may need flushing
  • Visit Settings → Permalinks in WordPress admin (saves/flushes rules)
  • Verify people have the birthdate field set
  • Check access control - user may not have access to those people