Best Practices - User Profile
Best Practices
- Use
bulkAddfor batch registration. Up to 50 profiles per request — significantly faster than individual calls for event registration imports. - Resolve virtual event ID → appGuid via App Registry first. Use
appCustomIdInfilter to map event IDs to app GUIDs before managing profiles (see App Registry API). - Use status transitions to track attendance lifecycle. Progress users through
created → registered → confirmed → attended → participatedfor accurate reporting. - Use
getFilteredfor reporting and analytics. Filter by status, date range, and fields to build attendance dashboards without downloading all profiles. - Use AppTokens for production access. Generate KS via
appToken.startSessionwith HMAC — keep admin secrets off application servers.
Common Integration Patterns
Event Registration Flow
# 1. Register the app in App Registry (one-time setup)
APP_GUID=$(curl -s -X POST "$KALTURA_APP_REGISTRY_URL/app-registry/add" \
-H "Authorization: Bearer $KALTURA_KS" \
-H "Content-Type: application/json" \
-d '{"appCustomId": "webinar-2025-q4", "appType": "ep", "appCustomName": "Q4 Webinar"}' \
| jq -r '.id')
# 2. Create user profile when they register
PROFILE_ID=$(curl -s -X POST "$KALTURA_USER_PROFILE_URL/user-profile/add" \
-H "Authorization: Bearer $KALTURA_KS" \
-H "Content-Type: application/json" \
-d "{
\"appGuid\": \"$APP_GUID\",
\"userId\": \"attendee@example.com\",
\"profileData\": {\"name\": \"John Smith\", \"company\": \"Tech Corp\"},
\"eventData\": {
\"regOrigin\": \"registration\",
\"attendanceStatus\": \"registered\",
\"userRegistrationType\": \"virtualAttendanceRequest\"
}
}" | jq -r '.id')
# 3. Confirm attendance
curl -s -X POST "$KALTURA_USER_PROFILE_URL/user-profile/update" \
-H "Authorization: Bearer $KALTURA_KS" \
-H "Content-Type: application/json" \
-d "{
\"id\": \"$PROFILE_ID\",
\"eventData\": {\"attendanceStatus\": \"confirmed\"}
}"
# 4. Mark as attended (during event — triggers firstAttendedStatusTime)
curl -s -X POST "$KALTURA_USER_PROFILE_URL/user-profile/update" \
-H "Authorization: Bearer $KALTURA_KS" \
-H "Content-Type: application/json" \
-d "{
\"id\": \"$PROFILE_ID\",
\"eventData\": {\"attendanceStatus\": \"attended\"}
}"
# 5. Get attendance stats
curl -s -X POST "$KALTURA_USER_PROFILE_URL/reports/eventDataStats" \
-H "Authorization: Bearer $KALTURA_KS" \
-H "Content-Type: application/json" \
-d "{
\"filter\": {\"appGuidIn\": [\"$APP_GUID\"]},
\"dimensions\": [\"attendanceStatus\"]
}"
Bulk Import Attendees
# Import a batch of invited users
curl -X POST "$KALTURA_USER_PROFILE_URL/user-profile/bulkAdd" \
-H "Authorization: Bearer $KALTURA_KS" \
-H "Content-Type: application/json" \
-d "[
{\"appGuid\": \"$APP_GUID\", \"userId\": \"vip1@example.com\", \"profileData\": {\"name\": \"VIP One\"}, \"eventData\": {\"regOrigin\": \"invite\", \"attendanceStatus\": \"invited\"}},
{\"appGuid\": \"$APP_GUID\", \"userId\": \"vip2@example.com\", \"profileData\": {\"name\": \"VIP Two\"}, \"eventData\": {\"regOrigin\": \"invite\", \"attendanceStatus\": \"invited\"}}
]"
Cross-Service Registration Data Retrieval
The full workflow for extracting registration data for a virtual event spans multiple services. The key linkage is that appCustomId in the App Registry equals the virtualEventId:
# Step 1: List virtual events to get event IDs
VIRTUAL_EVENT_ID=$(curl -s -X POST "$KALTURA_SERVICE_URL/service/virtualevent_virtualevent/action/list" \
-d "ks=$KALTURA_KS" -d "format=1" | jq -r '.objects[0].id')
# Step 2: Resolve virtual event ID → App GUID via App Registry
APP_GUID=$(curl -s -X POST "$KALTURA_APP_REGISTRY_URL/app-registry/list" \
-H "Authorization: Bearer $KALTURA_KS" \
-H "Content-Type: application/json" \
-d "{\"filter\": {\"appCustomIdIn\": [\"$VIRTUAL_EVENT_ID\"]}}" \
| jq -r '.objects[0].id')
# Step 3: Pull registration/attendance data
RESPONSE=$(curl -s -X POST "$KALTURA_USER_PROFILE_URL/user-profile/list" \
-H "Authorization: Bearer $KALTURA_KS" \
-H "Content-Type: application/json" \
-d "{
\"filter\": {\"appGuidIn\": [\"$APP_GUID\"]},
\"pager\": {\"offset\": 0, \"limit\": 500},
\"orderBy\": \"createdAt\",
\"includeTotalCount\": true
}")
# Step 4: Join with core user data for complete profiles
# User Profile has eventData (attendance) and profileData (custom fields)
# Core fields (email, firstName, lastName, company, jobTitle) are in user.list
USER_IDS=$(echo "$RESPONSE" | jq -r '[.objects[].userId] | join(",")')
curl -s -X POST "$KALTURA_SERVICE_URL/service/user/action/list" \
-d "ks=$KALTURA_KS" -d "format=1" \
-d "filter[objectType]=KalturaUserFilter" \
-d "filter[idIn]=$USER_IDS"
See the App Registry API section 12.4 for the complete cross-service diagram.
Incremental Data Pull
Use updatedAtGreaterThanOrEqual to pull only profiles changed since your last sync — essential for large-scale integrations:
# Pull profiles updated since your last watermark
LAST_SYNC="2025-06-14T00:00:00Z"
curl -s -X POST "$KALTURA_USER_PROFILE_URL/user-profile/list" \
-H "Authorization: Bearer $KALTURA_KS" \
-H "Content-Type: application/json" \
-d "{
\"filter\": {
\"appGuidIn\": [\"$APP_GUID\"],
\"updatedAtGreaterThanOrEqual\": \"$LAST_SYNC\"
},
\"pager\": {\"offset\": 0, \"limit\": 500},
\"orderBy\": \"updatedAt\"
}"
# Store the last record's updatedAt as your new watermark
For very large datasets (10K+ records), combine pagination with time-based windowing — use updatedAtLessThanOrEqual alongside updatedAtGreaterThanOrEqual to partition queries into daily or hourly windows.
Paginated Export
OFFSET=0
LIMIT=100
while true; do
RESPONSE=$(curl -s -X POST "$KALTURA_USER_PROFILE_URL/user-profile/list" \
-H "Authorization: Bearer $KALTURA_KS" \
-H "Content-Type: application/json" \
-d "{
\"filter\": {\"appGuidIn\": [\"$APP_GUID\"]},
\"pager\": {\"offset\": $OFFSET, \"limit\": $LIMIT},
\"orderBy\": \"createdAt\"
}")
COUNT=$(echo "$RESPONSE" | jq '.objects | length')
echo "Fetched $COUNT profiles (offset=$OFFSET)"
# Process profiles...
[ "$COUNT" -lt "$LIMIT" ] && break
OFFSET=$((OFFSET + LIMIT))
done
Re-register After Cancellation
After a user unregisters and their profile is soft-deleted, create a new profile to re-register them:
# Original profile was deleted (soft-delete)
# Create a fresh profile for the same user + app
curl -X POST "$KALTURA_USER_PROFILE_URL/user-profile/add" \
-H "Authorization: Bearer $KALTURA_KS" \
-H "Content-Type: application/json" \
-d "{
\"appGuid\": \"$APP_GUID\",
\"userId\": \"returning-user@example.com\",
\"profileData\": {\"name\": \"Returning User\"},
\"eventData\": {
\"regOrigin\": \"registration\",
\"attendanceStatus\": \"registered\"
}
}"
PII Deletion and Cleanup
When deleting event data for compliance (GDPR, data retention), follow this order:
- Resolve the event — Get the
appGuidfrom App Registry usingappCustomIdIn: [virtualEventId] - List user profiles for the event —
user-profile/listwithappGuidIn: [appGuid] - Check multi-event registration — For each userId, query
user-profile/listwithout appGuid filter to find profiles in other events. Flag users registered to multiple events. - Delete user profiles —
user-profile/deletefor each profile in this event - Delete KalturaUsers — Only delete users from
user.deleteif they have no profiles in other events. Flagged users retain their core user record.
Delete user profiles before deleting the underlying KalturaUser — this preserves the ability to re-invite the user to future events. The user profile creation step validates that the user exists via user.get, so the core user record must still be present when creating new profiles.
# Delete a profile, then check if the user can be fully deleted
curl -s -X POST "$KALTURA_USER_PROFILE_URL/user-profile/delete" \
-H "Authorization: Bearer $KALTURA_KS" \
-H "Content-Type: application/json" \
-d "{\"id\": \"$PROFILE_ID\"}"
# Check if user has profiles in other events before deleting the core user
OTHER_PROFILES=$(curl -s -X POST "$KALTURA_USER_PROFILE_URL/user-profile/list" \
-H "Authorization: Bearer $KALTURA_KS" \
-H "Content-Type: application/json" \
-d "{
\"filter\": {\"userIdIn\": [\"$USER_ID\"]},
\"pager\": {\"offset\": 0, \"limit\": 1},
\"includeTotalCount\": true
}" | jq '.totalCount')
if [ "$OTHER_PROFILES" -eq 0 ]; then
# Safe to delete the core user
curl -s -X POST "$KALTURA_SERVICE_URL/service/user/action/delete" \
-d "ks=$KALTURA_KS" -d "format=1" -d "userId=$USER_ID"
fi
Registration Reports
For CSV-based registration and engagement reports, the platform provides an async Reports Service. This is separate from the User Profile API's eventDataStats endpoint and produces downloadable CSV files.
Reports Base URL: https://reports.nvp1.ovp.kaltura.com/api/v1
Auth: Authorization: Bearer <KS>
Generate a Report
REPORTS_URL="https://reports.nvp1.ovp.kaltura.com/api/v1"
# Request report generation
SESSION_ID=$(curl -s -X POST "$REPORTS_URL/report/generate" \
-H "Authorization: Bearer $KALTURA_KS" \
-H "Content-Type: application/json" \
-d "{
\"reportParameters\": {\"appGuid\": \"$APP_GUID\"},
\"reportName\": \"registration\"
}" | jq -r '.sessionId')
Poll and Download
The generate call returns a sessionId. Poll the serve endpoint until the report is ready:
# Poll until ready (statusOnly: true returns status without CSV)
while true; do
STATUS=$(curl -s -X POST "$REPORTS_URL/report/serve" \
-H "Authorization: Bearer $KALTURA_KS" \
-H "Content-Type: application/json" \
-d "{\"sessionId\": \"$SESSION_ID\", \"statusOnly\": true}" \
| jq -r '.status')
[ "$STATUS" = "completed" ] && break
sleep 2
done
# Download the CSV
curl -s -X POST "$REPORTS_URL/report/serve" \
-H "Authorization: Bearer $KALTURA_KS" \
-H "Content-Type: application/json" \
-d "{\"sessionId\": \"$SESSION_ID\", \"statusOnly\": false}"
Reports may reflect cached data with a freshness window of 10-15 minutes. For real-time attendance status, use the User Profile list endpoint directly.
Engagement Reports (API v3)
Per-user session engagement data is available via the core Kaltura Reports API using specific report IDs:
| Report ID | Purpose | Key Fields |
|---|---|---|
| 3030 | Live/VOD engagement summary | user_id, email, plays, sum_minutes_viewed, live_engagement_rate |
| 3035 | Device/OS breakdown | device_used, os_used, view_time, total_completion_rate |
| 3037 | Per-user session detail | date, user_id, entry_id, entry_name, sum_minutes_viewed |
| 6000 | Meeting Room session reports | entry_id, entry_name, session_start_time, session_end_time |
| 6001 | Meeting Room attendee reports | entry_id, user_id, user_name, user_type, meeting_view_time |
# Get per-user session engagement for a virtual event
curl -s -X POST "$KALTURA_SERVICE_URL/service/report/action/getCsvFromStringParams" \
-d "ks=$KALTURA_KS" \
-d "format=1" \
-d "id=3037" \
-d "params=from_date=$FROM_UNIX;to_date=$TO_UNIX;virtual_event_ids=$VIRTUAL_EVENT_ID"