Platform Characteristics
- Target Market: Square merchants & small businesses
- Integration: Deep Square ecosystem integration
- Employee Count: Optimized for 1-50 employees
- Pricing Model: Per employee + contractor pricing
Square Payroll is part of Square’s comprehensive business management suite, designed primarily for small businesses already using Square for payments. While it offers seamless integration within the Square ecosystem, external integrations require special access and careful planning.
Platform Characteristics
API Limitations
Square provides several APIs that work together for workforce management:
Core employee and team member management:
// Retrieve team membersconst listTeamMembers = async () => { const response = await fetch('https://connect.squareup.com/v2/team-members', { headers: { 'Authorization': `Bearer ${ACCESS_TOKEN}`, 'Square-Version': '2024-12-18' } }); return response.json();};
Endpoints:
GET /v2/team-members
- List all team membersGET /v2/team-members/{id}
- Get specific memberPOST /v2/team-members
- Create team memberPUT /v2/team-members/{id}
- Update memberTime tracking and scheduling capabilities:
// Create a shiftconst createShift = async (employeeId, startAt, endAt) => { const response = await fetch('https://connect.squareup.com/v2/labor/shifts', { method: 'POST', headers: { 'Authorization': `Bearer ${ACCESS_TOKEN}`, 'Square-Version': '2024-12-18', 'Content-Type': 'application/json' }, body: JSON.stringify({ shift: { employee_id: employeeId, start_at: startAt, end_at: endAt, wage: { title: 'Cashier', hourly_rate: { amount: 1500, // $15.00 currency: 'USD' } } } }) }); return response.json();};
Features:
New in 2024, manage job assignments and wages:
// Create a job roleconst createJob = async () => { const response = await fetch('https://connect.squareup.com/v2/labor/jobs', { method: 'POST', headers: { 'Authorization': `Bearer ${ACCESS_TOKEN}`, 'Square-Version': '2024-12-18', 'Content-Type': 'application/json' }, body: JSON.stringify({ job: { title: 'Store Manager', hourly_rate: { amount: 2500, currency: 'USD' }, is_tip_eligible: false } }) }); return response.json();};
Create Square Application
Request Payroll API Access
To: developer-support@squareup.comSubject: Payroll API Access Request
Company: [Your Company]Use Case: Integration with Tallyfy for workflow automationExpected Volume: [Number of merchants]Technical Contact: [Your email]
Implement OAuth 2.0
// OAuth authorization URLconst authUrl = `https://connect.squareup.com/oauth2/authorize? client_id=${CLIENT_ID}& response_type=code& scope=EMPLOYEES_READ+EMPLOYEES_WRITE+TIMECARDS_READ+TIMECARDS_WRITE& state=${STATE}`;
// Token exchangeconst getAccessToken = async (code) => { const response = await fetch('https://connect.squareup.com/oauth2/token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ client_id: CLIENT_ID, client_secret: CLIENT_SECRET, grant_type: 'authorization_code', code: code }) }); return response.json();};
Test in Sandbox
// Use sandbox URL for testingconst SANDBOX_URL = 'https://connect.squareupsandbox.com/v2';const SANDBOX_TOKEN = 'EAAAEA...'; // Sandbox access token
// Test API callconst testConnection = async () => { const response = await fetch(`${SANDBOX_URL}/team-members`, { headers: { 'Authorization': `Bearer ${SANDBOX_TOKEN}`, 'Square-Version': '2024-12-18' } }); return response.json();};
Square provides webhooks for real-time notifications:
// Register webhook subscriptionconst createWebhook = async () => { const response = await fetch('https://connect.squareup.com/v2/webhooks/subscriptions', { method: 'POST', headers: { 'Authorization': `Bearer ${ACCESS_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ subscription: { name: 'Tallyfy Integration', notification_url: 'https://app.tallyfy.com/webhooks/square', event_types: [ 'team_member.created', 'team_member.updated', 'shift.created', 'shift.updated', 'timecard.created', 'timecard.updated' ] } }) }); return response.json();};
// Webhook handlerapp.post('/webhooks/square', (req, res) => { const signature = req.headers['x-square-signature'];
// Verify webhook signature if (verifySquareWebhook(req.body, signature)) { const { type, data } = req.body;
switch(type) { case 'team_member.created': handleNewEmployee(data.object.team_member); break; case 'shift.updated': handleShiftChange(data.object.shift); break; } }
res.sendStatus(200);});
class SquarePayrollIntegration { async onboardNewEmployee(employeeData) { // Step 1: Create team member in Square const teamMember = await this.square.createTeamMember({ given_name: employeeData.firstName, family_name: employeeData.lastName, email_address: employeeData.email, phone_number: employeeData.phone, assigned_location_ids: [employeeData.locationId] });
// Step 2: Assign job and wage const jobAssignment = await this.square.createJobAssignment({ team_member_id: teamMember.id, job_id: employeeData.jobId, pay_type: 'HOURLY', hourly_rate: { amount: employeeData.hourlyRate * 100, currency: 'USD' } });
// Step 3: Create onboarding process in Tallyfy const process = await this.tallyfy.createProcess({ template: 'employee-onboarding', data: { employeeName: `${employeeData.firstName} ${employeeData.lastName}`, squareTeamMemberId: teamMember.id, startDate: employeeData.startDate, position: employeeData.position } });
return { teamMember, jobAssignment, process }; }}
// Monitor timecard submissionsconst processTimecards = async () => { // Get unapproved timecards const timecards = await square.listTimecards({ limit: 50, clockin_time_range: { start_at: weekStart, end_at: weekEnd } });
for (const timecard of timecards) { if (!timecard.approved) { // Create approval process await tallyfy.createProcess({ template: 'timecard-approval', data: { employeeId: timecard.employee_id, hours: calculateHours(timecard), overtime: calculateOvertime(timecard), managerId: getManager(timecard.employee_id) } }); } }};
// Schedule weekly processingschedule.weekly(processTimecards);
class SquareRateLimiter { constructor() { this.queue = []; this.processing = false; this.delay = 100; // Start with 100ms between requests }
async execute(fn) { return new Promise((resolve, reject) => { this.queue.push({ fn, resolve, reject }); this.process(); }); }
async process() { if (this.processing || this.queue.length === 0) return;
this.processing = true;
while (this.queue.length > 0) { const { fn, resolve, reject } = this.queue.shift();
try { const result = await fn();
// Check for rate limit headers if (result.headers && result.headers['retry-after']) { this.delay = parseInt(result.headers['retry-after']) * 1000; } else { // Gradually reduce delay on success this.delay = Math.max(100, this.delay * 0.9); }
resolve(result); } catch (error) { if (error.status === 429) { // Rate limited - increase delay this.delay = Math.min(60000, this.delay * 2); this.queue.unshift({ fn, resolve, reject }); // Retry } else { reject(error); } }
await sleep(this.delay); }
this.processing = false; }}
For businesses without API access:
# Export payroll data from Square# Process CSV files for Tallyfy
import pandas as pdimport requestsfrom datetime import datetime
class SquareCSVProcessor: def __init__(self, tallyfy_api): self.tallyfy = tallyfy_api
def process_payroll_export(self, csv_file): """Process Square Payroll export CSV""" df = pd.read_csv(csv_file)
for _, row in df.iterrows(): if row['Type'] == 'New Hire': self.create_onboarding(row) elif row['Type'] == 'Termination': self.create_offboarding(row) elif row['Hours'] > 40: self.flag_overtime(row)
def create_onboarding(self, employee): """Create onboarding process for new hire""" self.tallyfy.create_process({ 'template': 'new-hire-onboarding', 'data': { 'name': employee['Employee Name'], 'start_date': employee['Start Date'], 'location': employee['Location'], 'position': employee['Position'] } })
// Sync employees across locationsconst syncMultiLocationTeam = async () => { const locations = await square.listLocations();
for (const location of locations) { const teamMembers = await square.listTeamMembers({ location_id: location.id });
// Create location-specific processes await tallyfy.createProcess({ template: 'location-staff-review', data: { location: location.name, employeeCount: teamMembers.length, employees: teamMembers.map(tm => ({ id: tm.id, name: `${tm.given_name} ${tm.family_name}`, role: tm.assigned_role })) } }); }};
// Monitor labor law complianceconst checkCompliance = async () => { const shifts = await square.listShifts({ begin_time: lastWeek, end_time: now });
const violations = [];
for (const shift of shifts) { // Check for violations if (shift.hours > 10) { violations.push({ type: 'excessive_hours', employee: shift.employee_id, date: shift.start_at }); }
// Check break compliance if (!shift.breaks || shift.breaks.length === 0) { violations.push({ type: 'missing_break', employee: shift.employee_id, date: shift.start_at }); } }
if (violations.length > 0) { await tallyfy.createProcess({ template: 'compliance-review', priority: 'high', data: { violations } }); }};
Solution:
Solution:
// Implement retry logicconst retryWithBackoff = async (fn, retries = 3) => { for (let i = 0; i < retries; i++) { try { return await fn(); } catch (error) { if (error.status === 429 && i < retries - 1) { await sleep(Math.pow(2, i) * 1000); } else { throw error; } } }};
While Square Payroll offers powerful features within the Square ecosystem, external integration requires careful planning and potentially special approval. For quick implementation, consider unified API providers. For deeper integration, be prepared for the approval process and conservative rate limit management.