def create_participant(worker_id, hit_id, assignment_id, mode):
    """Create a participant.

    This route is hit early on. Any nodes the participant creates will be
    defined in reference to the participant object. You must specify the
    worker_id, hit_id, assignment_id, and mode in the url.
    """
    # Lock the table, triggering multiple simultaneous accesses to fail
    try:
        session.connection().execute("LOCK TABLE participant IN EXCLUSIVE MODE NOWAIT")
    except exc.OperationalError as e:
        e.orig = TransactionRollbackError()
        raise e

    missing = [p for p in (worker_id, hit_id, assignment_id) if p == "undefined"]
    if missing:
        msg = "/participant POST: required values were 'undefined'"
        return error_response(error_type=msg, status=403)

    fingerprint_hash = request.args.get("fingerprint_hash")
    try:
        fingerprint_found = models.Participant.query.filter_by(
            fingerprint_hash=fingerprint_hash
        ).one_or_none()
    except MultipleResultsFound:
        fingerprint_found = True

    if fingerprint_hash and fingerprint_found:
        db.logger.warning("Same browser fingerprint detected.")

        if mode == "live":
            return error_response(
                error_type="/participant POST: Same participant dectected.", status=403
            )

    already_participated = models.Participant.query.filter_by(
        worker_id=worker_id
    ).one_or_none()

    if already_participated:
        db.logger.warning("Worker has already participated.")
        return error_response(
            error_type="/participant POST: worker has already participated.", status=403
        )

    duplicate = models.Participant.query.filter_by(
        assignment_id=assignment_id, status="working"
    ).one_or_none()

    if duplicate:
        msg = """
            AWS has reused assignment_id while existing participant is
            working. Replacing older participant {}.
        """
        app.logger.warning(msg.format(duplicate.id))
        q.enqueue(worker_function, "AssignmentReassigned", None, duplicate.id)

    # Count working or beyond participants.
    nonfailed_count = (
        models.Participant.query.filter(
            (models.Participant.status == "working")
            | (models.Participant.status == "overrecruited")
            | (models.Participant.status == "submitted")
            | (models.Participant.status == "approved")
        ).count()
        + 1
    )

    recruiter_name = request.args.get("recruiter", "undefined")
    if not recruiter_name or recruiter_name == "undefined":
        recruiter = recruiters.from_config(_config())
        if recruiter:
            recruiter_name = recruiter.nickname

    # Create the new participant.
    participant = models.Participant(
        recruiter_id=recruiter_name,
        worker_id=worker_id,
        assignment_id=assignment_id,
        hit_id=hit_id,
        mode=mode,
        fingerprint_hash=fingerprint_hash,
    )

    exp = Experiment(session)

    overrecruited = exp.is_overrecruited(nonfailed_count)
    if overrecruited:
        participant.status = "overrecruited"

    session.add(participant)
    session.flush()  # Make sure we know the id for the new row
    result = {"participant": participant.__json__()}

    # Queue notification to others in waiting room
    if exp.quorum:
        quorum = {"q": exp.quorum, "n": nonfailed_count, "overrecruited": overrecruited}
        db.queue_message(WAITING_ROOM_CHANNEL, dumps(quorum))
        result["quorum"] = quorum

    # return the data
    return success_response(**result)