Employee Onboarding Workflow

Complete code to onboard new employees with card and PIN in 7 minutes

7 mins
Intermediate

Complete, production-ready code to onboard new employees. Creates person record, assigns to group, issues card and PIN backup.

The Workflow

Scenario: New employee starts Monday. They need permanent building access.

What we'll do

•
Create person record
•
Assign to "Employees" group
•
Issue card credential
•
Issue PIN credential (backup)
•
Return credentials to send to employee

Time: About 2-3 seconds per employee

Prerequisites

You need

•
OAuth access token with account.person and account.person scopes
•
Employee group ID (usually 1)
•
Card number from physical card inventory
•
Credential type IDs (card typically 5, PIN typically 6)

Check your credential types:

bash
curl -X GET "https://api.doorflow.com/api/3/credential_types" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Bash Script

Complete onboarding script you can run from command line:

bash
#!/bin/bash
# Employee Onboarding Script
# Usage: ./onboard_employee.sh "John" "Doe" "john.doe@company.com" "1234567890"

FIRST_NAME=$1
LAST_NAME=$2
EMAIL=$3
CARD_NUMBER=$4

ACCESS_TOKEN="your_access_token_here"
BASE_URL="https://api.doorflow.com/api/3"

echo "Onboarding: $FIRST_NAME $LAST_NAME..."

# Step 1: Create person and assign to Employees group (ID 1)
PERSON_RESPONSE=$(curl -s -X POST "$BASE_URL/people" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"first_name\": \"$FIRST_NAME\",
    \"last_name\": \"$LAST_NAME\",
    \"email\": \"$EMAIL\",
    \"enabled\": true,
    \"group_ids\": [1]
  }")

PERSON_ID=$(echo $PERSON_RESPONSE | jq -r '.id')

if [ "$PERSON_ID" == "null" ]; then
  echo "Error creating person:"
  echo $PERSON_RESPONSE | jq '.'
  exit 1
fi

echo "[OK] Person created (ID: $PERSON_ID)"

# Step 2: Issue card credential (type ID 5)
CARD_RESPONSE=$(curl -s -X POST "$BASE_URL/people/$PERSON_ID/credentials" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"person_credential\": {
      \"credential_type_id\": 5,
      \"value\": \"$CARD_NUMBER\",
      \"enabled\": true
    }
  }")

CARD_ID=$(echo $CARD_RESPONSE | jq -r '.id')
echo "[OK] Card issued (ID: $CARD_ID, Number: $CARD_NUMBER)"

# Step 3: Issue auto-generated PIN (backup)
PIN_RESPONSE=$(curl -s -X POST "$BASE_URL/people/$PERSON_ID/credentials" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "person_credential": {
      "credential_type_id": 6,
      "value": "******",
      "enabled": true
    }
  }')

PIN_VALUE=$(echo $PIN_RESPONSE | jq -r '.value')
PIN_ID=$(echo $PIN_RESPONSE | jq -r '.id')
echo "[OK] PIN issued (ID: $PIN_ID, PIN: $PIN_VALUE)"

# Step 4: Verify
VERIFY_RESPONSE=$(curl -s -X GET "$BASE_URL/people/$PERSON_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN")

echo ""
echo "[OK] Onboarding complete!"
echo ""
echo "Summary:"
echo "  Name: $FIRST_NAME $LAST_NAME"
echo "  Email: $EMAIL"
echo "  Person ID: $PERSON_ID"
echo "  Card Number: $CARD_NUMBER"
echo "  PIN: $PIN_VALUE"
echo ""
echo "Next steps:"
echo "  1. Give employee their physical card (number: $CARD_NUMBER)"
echo "  2. Send PIN via secure channel (SMS/email): $PIN_VALUE"
echo "  3. Test access at a door"

Save as: onboard_employee.sh

Run:

bash
chmod +x onboard_employee.sh
./onboard_employee.sh "Jane" "Smith" "jane@company.com" "9876543210"

JavaScript/Node.js Version

Async function you can integrate into your application:

javascript
async function onboardEmployee(firstName, lastName, email, cardNumber) {
  const accessToken = process.env.DOORFLOW_ACCESS_TOKEN;
  const baseURL = 'https://api.doorflow.com/api/3';

  try {
    // Step 1: Create person
    console.log(`Creating person: ${firstName} ${lastName}...`);

    const personResponse = await fetch(`${baseURL}/people`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        first_name: firstName,
        last_name: lastName,
        email: email,
        enabled: true,
        group_ids: [1], // Employees group
      }),
    });

    if (!personResponse.ok) {
      const error = await personResponse.json();
      throw new Error(`Failed to create person: ${error.error || error.message}`);
    }

    const person = await personResponse.json();
    console.log(`[OK] Person created (ID: ${person.id})`);

    // Step 2: Issue card
    console.log('Issuing card credential...');

    const cardResponse = await fetch(`${baseURL}/people/${person.id}/credentials`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        person_credential: {
          credential_type_id: 5, // Card
          value: cardNumber,
          enabled: true,
        },
      }),
    });

    if (!cardResponse.ok) {
      throw new Error(`Failed to issue card: ${await cardResponse.text()}`);
    }

    const card = await cardResponse.json();
    console.log(`[OK] Card issued (Number: ${cardNumber})`);

    // Step 3: Issue auto-generated PIN
    console.log('Issuing backup PIN...');

    const pinResponse = await fetch(`${baseURL}/people/${person.id}/credentials`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        person_credential: {
          credential_type_id: 6, // PIN
          value: '******', // Auto-generate secure PIN
          enabled: true,
        },
      }),
    });

    if (!pinResponse.ok) {
      throw new Error(`Failed to issue PIN: ${await pinResponse.text()}`);
    }

    const pin = await pinResponse.json();
    console.log(`[OK] PIN issued: ${pin.value}`);

    console.log('[OK] Onboarding complete!');

    return {
      personId: person.id,
      firstName: firstName,
      lastName: lastName,
      email: email,
      cardNumber: cardNumber,
      pin: pin.value,
    };

  } catch (error) {
    console.error('Onboarding failed:', error);
    throw error;
  }
}

// Usage
onboardEmployee('Jane', 'Smith', 'jane@company.com', '9876543210')
  .then(result => {
    console.log('\nOnboarding complete:', result);
    console.log('\nNext steps:');
    console.log('1. Give employee card:', result.cardNumber);
    console.log('2. Send PIN securely:', result.pin);

    // Send welcome email with PIN
    // sendWelcomeEmail(result.email, result.pin);
  })
  .catch(error => {
    console.error('Error:', error.message);
  });

Bulk Onboarding

Onboard multiple employees from CSV:

javascript
const fs = require('fs');
const csv = require('csv-parser');

async function bulkOnboardEmployees(csvFilePath) {
  const employees = [];

  // Read CSV
  await new Promise((resolve, reject) => {
    fs.createReadStream(csvFilePath)
      .pipe(csv())
      .on('data', (row) => {
        employees.push({
          firstName: row.first_name,
          lastName: row.last_name,
          email: row.email,
          cardNumber: row.card_number,
        });
      })
      .on('end', resolve)
      .on('error', reject);
  });

  console.log(`Onboarding ${employees.length} employees...`);

  // Onboard each employee with delay to respect rate limits
  const results = [];
  for (const employee of employees) {
    try {
      const result = await onboardEmployee(
        employee.firstName,
        employee.lastName,
        employee.email,
        employee.cardNumber
      );
      results.push({ success: true, ...result });

      // Delay to avoid rate limits (120 req/minute = ~0.5 seconds per request)
      await new Promise(resolve => setTimeout(resolve, 4000));

    } catch (error) {
      console.error(`Failed to onboard ${employee.firstName} ${employee.lastName}:`, error.message);
      results.push({
        success: false,
        firstName: employee.firstName,
        lastName: employee.lastName,
        error: error.message,
      });
    }
  }

  // Generate report
  const successful = results.filter(r => r.success);
  const failed = results.filter(r => !r.success);

  console.log(`\n[OK] Bulk onboarding complete`);
  console.log(`Successful: ${successful.length}`);
  console.log(`Failed: ${failed.length}`);

  return results;
}

// Usage
bulkOnboardEmployees('./new_employees.csv')
  .then(results => {
    // Save results to file
    fs.writeFileSync(
      './onboarding_results.json',
      JSON.stringify(results, null, 2)
    );
    console.log('Results saved to onboarding_results.json');
  });

CSV format (new_employees.csv):

csv
first_name,last_name,email,card_number
Jane,Smith,jane@company.com,9876543210
Bob,Jones,bob@company.com,1234567890

Error Handling

Always handle errors gracefully:

javascript
async function onboardEmployeeSafely(firstName, lastName, email, cardNumber) {
  try {
    return await onboardEmployee(firstName, lastName, email, cardNumber);
  } catch (error) {
    // Log error
    console.error('Onboarding error:', {
      employee: `${firstName} ${lastName}`,
      email: email,
      error: error.message,
      timestamp: new Date(),
    });

    // Notify admin
    // await notifyAdmin(`Onboarding failed for ${email}: ${error.message}`);

    // Return error result instead of throwing
    return {
      success: false,
      firstName,
      lastName,
      email,
      error: error.message,
    };
  }
}

Common Errors

"Group not found"

  • Group ID doesn't exist
  • Check available groups: GET /api/3/groups

"Duplicate credential value"

  • Card number already in use
  • Verify card number is correct and not already assigned

"Invalid credential_type_id"

  • Credential type not available in your account
  • Check credential types: GET /api/3/credential_types

"Person already exists"

  • Email address already in system
  • Check existing people: GET /api/3/people?email=user@example.com

Best Practices

1. Validate inputs before calling API:

javascript
function validateEmployee(firstName, lastName, email, cardNumber) {
  if (!firstName || !lastName) {
    throw new Error('First and last name required');
  }

  if (!email || !email.includes('@')) {
    throw new Error('Valid email required');
  }

  if (!cardNumber || !/^\d+$/.test(cardNumber)) {
    throw new Error('Valid card number required (numeric only)');
  }
}

2. Always issue backup credentials

•
Card as primary (fastest access)
•
PIN as backup (when card is forgotten)
•
Consider mobile credential for tech-savvy employees

3. Store results for audit trail:

javascript
await db.insert('onboarding_log', {
  person_id: result.personId,
  card_number: result.cardNumber,
  pin: encrypt(result.pin), // Encrypt sensitive data
  onboarded_by: currentUser.id,
  onboarded_at: new Date(),
});

4. Send credentials securely

•
Email PIN to user's work email
•
Don't include both card number and PIN in same email
•
Consider SMS for PIN delivery
•
Use encrypted channels when possible

5. Test before giving to employee:

javascript
// Verify person can be retrieved
const person = await fetch(`${baseURL}/people/${personId}`, {
  headers: { 'Authorization': `Bearer ${accessToken}` }
});

// Verify credentials exist
const credentials = await fetch(`${baseURL}/people/${personId}/credentials`, {
  headers: { 'Authorization': `Bearer ${accessToken}` }
});

console.log(`Person has ${credentials.length} credentials`);

Integration Examples

Integrate with HR system

javascript
// When employee added in HR system
hrSystem.on('employee.created', async (employee) => {
  try {
    // Get next available card from inventory
    const cardNumber = await cardInventory.getNextAvailableCard();

    // Onboard in DoorFlow
    const result = await onboardEmployee(
      employee.firstName,
      employee.lastName,
      employee.workEmail,
      cardNumber
    );

    // Mark card as issued in inventory
    await cardInventory.markCardIssued(cardNumber, result.personId);

    // Send welcome email with PIN
    await emailService.sendWelcomeEmail(employee.workEmail, {
      cardNumber: cardNumber,
      pin: result.pin,
    });

    console.log(`[OK] ${employee.firstName} ${employee.lastName} onboarded`);

  } catch (error) {
    // Notify HR team of failure
    await hrSystem.addNote(employee.id, `DoorFlow onboarding failed: ${error.message}`);
  }
});

Integrate with Slack

javascript
// Slack command: /onboard Jane Smith jane@company.com 1234567890
slackApp.command('/onboard', async ({ command, ack, respond }) => {
  await ack();

  const [firstName, lastName, email, cardNumber] = command.text.split(' ');

  try {
    const result = await onboardEmployee(firstName, lastName, email, cardNumber);

    await respond({
      text: `${firstName} ${lastName} onboarded successfully!`,
      blocks: [
        {
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: `*Onboarding Complete*\n\nName: ${firstName} ${lastName}\nEmail: ${email}\nCard: ${cardNumber}\nPIN: ||${result.pin}|| (click to reveal)`,
          },
        },
      ],
    });

  } catch (error) {
    await respond({
      text: `Onboarding failed: ${error.message}`,
    });
  }
});

Required OAuth Scopes

  • account.person - Create and manage people
  • account.person - Issue credentials
  • account.group.readonly - Verify groups exist

Next Steps

•
[Employee Offboarding] - Revoke access when employee leaves
•
[Visitor Management] - Temporary access for visitors
•
[Contractor Access] - Time-limited access for contractors

Learn more

•
[Card Credentials] - Details about card types
•
[PIN Credentials] - Details about PINs
•
[Events and Audit Trail] - Monitor employee access