Setup accounting integration
How to fetch payroll journal entries from Salsa and export them to your accounting system.
Overview
This guide is for Partners building accounting integrations who need to extract payroll journal data from Salsa and publish it to an external accounting provider. It covers the full workflow: enabling accounting for employers, mapping payroll elements to your chart of accounts, and exporting journal entries after each payroll run.
After running payroll through Salsa, you can:
- Fetch journal entries - Get calculated debits/credits for each payroll run
- Export to your accounting system - Use the journal data to create entries in your system
- Mark as exported - Notify Salsa that the journal has been processed
Each journal entry includes properly categorized line items (wages, taxes, deductions) mapped to your chart of accounts.
Integration Flow
Step 1: Enable Accounting for Employer
Enable accounting integration for each employer:
mutation CreateAccountingConfiguration {
createAccountingConfiguration(input: {
employerId: "emp_abc123"
provider: MANUAL
workerPaymentExportTrigger: ON_CLEARANCE
}) {
accountingConfiguration {
id
provider
workerPaymentExportTrigger
}
}
}Export Trigger Options:
| Value | Description |
|---|---|
ON_CLEARANCE | (Recommended) Journal ready after payment has cleared. Safer for accounting accuracy. |
ON_CONFIRM | Journal ready immediately when payroll is confirmed. Faster but may have timing mismatches. |
Step 2: Setup Account Mappings
Map payroll elements to your chart of accounts. This determines how journal lines are categorized.
Query Available Mappings
First, query to discover what elements need mapping and get their mappingId values:
query GetMappingRequirements {
employer(id: "emp_abc123") {
# Current mapping state (shows mappingId for each element)
payrollJournalExclusiveAccountMapping {
bankAccounts {
mappingId
bankAccount { id displayName }
account { id name } # null if not yet mapped
}
pay {
wages {
defaultAccounts { mappingId accountType account { id name } }
byPayrollElementType {
mappingId
payrollElementType { id name }
accountType
account { id name }
}
}
reimbursements {
defaultAccounts { mappingId accountType account { id name } }
byPayrollElementType { mappingId payrollElementType { id name } account { id name } }
}
}
deductions {
nonTaxes {
defaultAccounts { mappingId accountType account { id name } }
byPayrollElementType { mappingId payrollElementType { id name } account { id name } }
}
}
contributions {
taxes {
defaultAccounts { mappingId accountType account { id name } }
byTax { mappingId tax { id name } accountType account { id name } }
}
nonTaxes {
defaultAccounts { mappingId accountType account { id name } }
byPayrollElementType { mappingId payrollElementType { id name } account { id name } }
}
}
}
}
}The response provides:
mappingId- Required identifier for each mapping entryaccountType- The expected account type (ASSET, EXPENSE, LIABILITY)account- Currently mapped account (null if not yet configured)payrollElementType/tax- The specific element being mapped
Use these mappingId values when setting up mappings.
Configure Account Mappings
Create accounts and map them to payroll elements. Use externalId to store your accounting system's account codes:
mutation UpdatePayrollJournalAccountMapping {
updatePayrollJournalAccountMapping(input: {
employerId: "emp_abc123"
bankAccounts: [
{
mappingId: "bank_primary"
accountReference: {
createAccount: {
name: "Payroll Clearing Account"
type: ASSET
externalId: "1000"
}
}
}
]
pay: {
wages: {
mapping: {
exclusive: {
defaultAccounts: [
{
mappingId: "wages_expense"
accountReference: {
createAccount: {
name: "Wages Expense"
type: EXPENSE
externalId: "6000"
}
}
}
]
}
}
}
reimbursements: {
mapping: {
exclusive: {
defaultAccounts: [
{
mappingId: "reimb_expense"
accountReference: {
createAccount: {
name: "Reimbursements Expense"
type: EXPENSE
externalId: "6100"
}
}
}
]
}
}
}
}
deductions: {
nonTaxes: {
mapping: {
exclusive: {
defaultAccounts: [
{
mappingId: "deductions_liability"
accountReference: {
createAccount: {
name: "Deductions Payable"
type: LIABILITY
externalId: "2100"
}
}
}
]
}
}
}
}
contributions: {
taxes: {
mapping: {
exclusive: {
defaultAccounts: [
{
mappingId: "tax_liability"
accountReference: {
createAccount: {
name: "Payroll Taxes Payable"
type: LIABILITY
externalId: "2200"
}
}
}
]
}
}
}
nonTaxes: {
mapping: {
exclusive: {
defaultAccounts: [
{
mappingId: "contrib_expense"
accountReference: {
createAccount: {
name: "Benefits Expense"
type: EXPENSE
externalId: "6200"
}
}
},
{
mappingId: "contrib_expense"
accountReference: {
createAccount: {
name: "Liability for 401k Match"
type: LIABILITY
externalId: "6201"
}
}
}
]
}
}
}
}
}) {
employer { id }
bankAccounts { mappingId account { id name externalId } }
}
}Account Types:
| Type | Use For |
|---|---|
ASSET | Bank accounts, clearing accounts |
EXPENSE | Wages, reimbursements, employer tax contributions |
LIABILITY | Tax withholdings, deductions payable |
Tip: The externalId field is returned in journal entries, making it easy to map to your accounting system.
Mapping Categories:
| Category | Description | Typical Account Type |
|---|---|---|
bankAccounts | Balancing entry (credit side) | ASSET |
pay.wages | Employee compensation | EXPENSE |
pay.reimbursements | Expense reimbursements | EXPENSE |
contributions.taxes | Employer tax contributions | EXPENSE |
contributions.nonTaxes | Non-tax employer contributions (401k match, etc.) | EXPENSE |
deductions.nonTaxes | Employee deductions (benefits, garnishments) | LIABILITY |
Mapping Strategies:
defaultAccounts- Use same account(s) for all items in categorybyPayrollElementType- Specify different accounts per payroll element typebyTax- Specify different accounts per tax type (forcontributions.taxes)
Step 3: Receive & Export Journals
Webhook Events
You'll receive webhooks when journal transmissions occur:
| Event | Description |
|---|---|
Transmission.processing | Journal generation has started |
Transmission.completed | Journal is ready to export |
Transmission.failed | Journal generation failed |
Webhook Payload:
{
"data": {
"attributes": {
"category": "LIFECYCLE",
"createdAt": "2025-12-30T09:32:12.645035",
"employerId": "emp_123",
"transmissionId": "trans_xyz789",
"transmissionType": "ACCOUNTING_PAYROLL_JOURNAL",
"updatedAt": "2025-12-30T09:32:12.645035"
},
"type": "Transmission.completed"
}
}Fetch Journal by ID
When you receive a Transmission.completed webhook, use the transmissionId to fetch the journal:
query GetTransmission {
transmission(id: "trans_xyz789") {
id
status
... on PayrollJournalManualTransmission {
journalEntry {
transactionDate
totalDebitAmount
totalCreditAmount
lines {
account { accountId name externalId }
amount
category
postingType
}
}
csvDocument { links { url } }
}
}
}Journal Entry Response:
{
"journalEntry": {
"transactionDate": "2024-01-15",
"totalDebitAmount": "10000",
"totalCreditAmount": "10000",
"lines": [
{
"account": { "accountId": "acct_123", "name": "Wages Expense", "externalId": "6000" },
"amount": "8000",
"category": "WAGE",
"postingType": "DEBIT"
},
{
"account": { "accountId": "acct_456", "name": "Tax Liability", "externalId": "2100" },
"amount": "2000",
"category": "TAX_CONTRIBUTION",
"postingType": "DEBIT"
},
{
"account": { "accountId": "acct_789", "name": "Payroll Bank", "externalId": "1000" },
"amount": "10000",
"category": "BANK_ACCOUNT",
"postingType": "CREDIT"
}
]
}
}Line Categories:
| Category | Description |
|---|---|
BANK_ACCOUNT | Balancing entry (typically CREDIT) |
WAGE | Employee wages |
REIMBURSEMENT | Expense reimbursements |
TAX_CONTRIBUTION | Tax amounts |
NON_TAX_DEDUCTION | Non-tax deductions |
NON_TAX_CONTRIBUTION | Non-tax contributions |
Posting Types:
DEBIT- Increases assets/expenses, decreases liabilitiesCREDIT- Decreases assets/expenses, increases liabilities
Mark as Exported
After exporting the journal to your accounting system, mark it as completed:
mutation MarkTransmissionCompleted {
markTransmissionCompleted(input: {
transmissionId: "trans_xyz789"
notFulfilledRequirementIds: [""]
}) {
fulfilled {
id
}
notFulfilled {
id
}
}
}Handle Failed Transmissions
When a transmission fails, you can query for failed requirements and retry them.
Query Failed Requirements:
query GetFailedJournalRequirements {
employer(id: "emp_abc123") {
transmissionRequirements(
filterBy: {
transmissionType: [ACCOUNTING_PAYROLL_JOURNAL]
typeSpecificDetails: {
payrollJournal: {
latestTransmissionStatuses: [FAILED_TO_TRANSMIT]
}
}
}
limit: 20
offset: 0
) {
nodes {
id
canBeTransmitted
}
}
}
}Retry Failed Transmission:
mutation RetryFailedTransmission {
createTransmissionForRequirements(input: {
countryISO: USA
transmissionType: ACCOUNTING_PAYROLL_JOURNAL
transmissionRequirementIds: ["trmsnreq_xxx"]
}) {
transmission {
id
... on PayrollJournalManualTransmission {
journalEntry {
transactionDate
lines { account { name } amount postingType }
}
csvDocument { links { url } }
}
}
}
}CSV Export
Each journal also includes a CSV download option:
query GetTransmissionCSV {
transmission(id: "trans_xyz789") {
... on PayrollJournalManualTransmission {
csvDocument {
id
name
links {
url
expiresAt
}
}
}
}
}Use the url from links to download the CSV file. Links have an expiration time (expiresAt).
CSV Format (RFC 4180):
| Column | Description |
|---|---|
| Transaction Date | Journal entry date |
| Account Id | Salsa account ID |
| Account Name | Account display name |
| Account External Id | Your external system ID |
| Category | Line category (WAGE, TAX_CONTRIBUTION, etc.) |
| Posting Type | DEBIT or CREDIT |
| Amount | Line amount |
Reference: Query Current Configuration
Fetch an employer's accounting setup:
query GetEmployerAccounting {
employer(id: "emp_abc123") {
accountingConfiguration {
id
provider
workerPaymentExportTrigger
}
accountingAccounts(limit: 100) {
nodes {
id
name
type
externalId
}
}
}
}Updated 1 day ago
