Skip to content

REDCap Integration

Seamless integration with REDCap for participant enrollment and data synchronization.

Overview

Metricis integrates with REDCap to:

  • Import participants from REDCap projects
  • Export assessment data to REDCap
  • Sync bidirectionally for real-time updates
  • Map event-battery associations for visit scheduling
  • Support webhooks for automated triggers

Configuration

REDCap Setup

Configure REDCap connection in the portal:

{
  "site_id": "hospital_a",
  "redcap_url": "https://redcap.hospital.org/api/",
  "api_token": "your-api-token-here",
  "event_name": "baseline_arm_1",
  "participant_id_field": "record_id",
  "sync_enabled": true
}

Field Mapping

Map assessment metrics to REDCap fields:

{
  "simple_rt_mean": "cognitive_rt_mean",
  "simple_rt_sd": "cognitive_rt_sd",
  "cpt_dprime": "cpt_sensitivity",
  "cpt_beta": "cpt_criterion",
  "nback_accuracy_1": "nback_1back_acc",
  "nback_accuracy_2": "nback_2back_acc",
  "nback_accuracy_3": "nback_3back_acc",
  "digit_span_forward": "ds_forward_max",
  "digit_span_backward": "ds_backward_max"
}

Participant Import

Manual Import

// Import all participants from REDCap
await api.importParticipantsFromREDCap(study_id);

Automated Sync

Configure periodic sync (e.g., daily):

# Celery task
@celery_app.task
def sync_redcap_participants(study_id: str):
    redcap_service = REDCapService(study_id)
    new_participants = redcap_service.import_new_participants()
    # Process and create participants in Metricis

Webhook Integration

REDCap Data Entry Triggers (DET) can notify Metricis of new enrollments:

# app/routers/redcap.py
@router.post("/webhook")
async def redcap_webhook(request: Request):
    data = await request.form()
    project_id = data.get("project_id")
    record_id = data.get("record")

    # Import new participant
    await redcap_service.import_participant(project_id, record_id)

    return {"status": "success"}

Data Export

Automatic Export

After assessment completion:

# app/services/redcap_sync.py
async def sync_session_to_redcap(session_id: str):
    session = await get_session(session_id)
    participant = await get_participant(session.participant_id)
    redcap_config = await get_redcap_config(participant.study_id)

    # Map task summaries to REDCap fields
    redcap_record = {
        redcap_config.participant_id_field: participant.participant_id,
        "redcap_event_name": redcap_config.event_name,
    }

    for task_name, summary in session.task_summaries.items():
        for metric_name, metric_value in summary.items():
            redcap_field = redcap_config.field_mappings.get(f"{task_name}_{metric_name}")
            if redcap_field:
                redcap_record[redcap_field] = metric_value

    # Export to REDCap
    project = Project(redcap_config.redcap_url, redcap_config.api_token)
    project.import_records([redcap_record])

Manual Export

Export selected sessions:

// Portal UI
const exportToREDCap = async (sessionIds: string[]) => {
  await api.exportSessionsToREDCap(sessionIds);
};

Event-Battery Mapping

Map REDCap events to Metricis batteries:

{
  "baseline_arm_1": "baseline_battery",
  "month_3_arm_1": "followup_battery",
  "month_6_arm_1": "followup_battery",
  "month_12_arm_1": "final_battery"
}

When a participant reaches a REDCap event, Metricis automatically schedules the corresponding battery.

Data Synchronization

Pull from REDCap

Import data from REDCap:

def pull_from_redcap(study_id: str):
    project = get_redcap_project(study_id)

    # Get all records
    records = project.export_records()

    for record in records:
        participant_id = record["record_id"]
        # Update participant data in Metricis

Push to REDCap

Export data to REDCap:

def push_to_redcap(study_id: str, session_ids: list[str]):
    project = get_redcap_project(study_id)
    records = []

    for session_id in session_ids:
        session = get_session(session_id)
        # Map session data to REDCap record
        record = map_session_to_redcap(session)
        records.append(record)

    # Import all records at once
    project.import_records(records)

Bidirectional Sync

Keep both systems in sync:

  1. Enrollment - REDCap → Metricis (via webhook or scheduled pull)
  2. Assessment completion - Metricis → REDCap (automatic export)
  3. Status updates - REDCap ↔ Metricis (bidirectional sync)

Multi-Site Support

Configure multiple REDCap instances:

# app/config.py
SITE_CONFIGS = {
    "site_a": SiteConfig(
        site_id="site_a",
        name="Hospital A",
        redcap_url="https://redcap.hospitala.org/api/",
        token_env_var="REDCAP_TOKEN_SITE_A",
    ),
    "site_b": SiteConfig(
        site_id="site_b",
        name="Hospital B",
        redcap_url="https://redcap.hospitalb.org/api/",
        token_env_var="REDCAP_TOKEN_SITE_B",
    ),
}

Each site can have different: - REDCap URLs - API tokens - Event names - Field mappings

Error Handling

Sync Failures

Track failed sync attempts:

{
  "session_id": "sess_123",
  "sync_attempts": 3,
  "last_attempt": "2026-01-24T12:34:56Z",
  "last_error": "REDCap API rate limit exceeded",
  "status": "pending_retry"
}

Retry logic: - Exponential backoff (1min, 5min, 15min, 1hr) - Manual retry option in portal - Email notification after 3 failed attempts

Validation Errors

Handle REDCap validation errors:

try:
    project.import_records([record])
except RedcapError as e:
    if "value out of range" in str(e):
        # Log validation error
        logger.warning("redcap_validation_error", field=..., value=...)
        # Create query in Metricis
        await create_query(...)

Monitoring

Sync Status Dashboard

Portal displays REDCap sync status:

  • Last sync timestamp
  • Pending records
  • Failed syncs
  • Sync history

Logs

All REDCap operations are logged:

{
  "timestamp": "2026-01-24T12:34:56Z",
  "operation": "export_session",
  "session_id": "sess_123",
  "participant_id": "P001",
  "status": "success",
  "records_exported": 1,
  "duration_ms": 234
}

Security

API Token Management

  • Tokens stored as environment variables
  • Never exposed in logs or UI
  • Separate tokens per site
  • Regular token rotation recommended

Data Privacy

  • Only export aggregate summaries (not raw trials)
  • Configurable field mapping (exclude sensitive fields)
  • Audit trail of all exports

Testing

Sandbox Mode

Test REDCap integration without affecting production:

{
  "sandbox_mode": true,
  "redcap_url": "https://redcap.test.org/api/",
  "api_token": "test-token"
}

Mock REDCap Server

Use mock server for development:

# tests/mocks/redcap.py
class MockREDCapProject:
    def export_records(self):
        return [
            {"record_id": "001", "study_id": "STUDY-001"},
            {"record_id": "002", "study_id": "STUDY-001"},
        ]

    def import_records(self, records):
        return {"count": len(records)}

Next Steps