Skip to main content
Self-certification lets users review the income and employment data Truv retrieves and either confirm each record as representative or flag a discrepancy. Retrieve the results on the order to see which records the user certified and which they disputed.

When to use


Enable self-certification

Turn on self-certification per Customization Template in the dashboard, under Self-certification. The Enable self-employment income toggle chooses what the user certifies:

Retrieve certification results [Server-side]

Certification data is returned by the standard order-read endpoint, gated behind the certification query parameter. Call it after the user completes their review — the certification-completed webhook signals when results are ready.
GET /v1/orders/{id}/certifications/ is deprecated and does not return bank income. Use the certification query parameter on the order-read endpoint instead.

Certification modes

Records the user flags as not their own income are excluded in both modes.

Response structure

Each retrieved record carries a certification object inline. What’s certified depends on the template configuration — an order is one configuration or the other, never both. Bank income configuration. Payroll employments carry a certification under employers[].employments[]. Bank-detected income carries one under financial_accounts[].bank_income_sources[], and user-added income under financial_accounts[].additional_income_sources[]. Bank accounts[] are not certified in this configuration — they carry no certification object.
Bank accounts configuration. Payroll employments are certified as above, and bank accounts[] carry a certification. Account certifications hold decision, rejection_reason, and rejection_comment only — they never include certified_data, and bank_income_sources / additional_income_sources are not certified.
certified_data is the user-attested snapshot. Its numeric fields can be null — a payroll employment may report hours_per_month while leaving monthly_income empty.

Decision values

Rejection reasons

When the decision is not_representative, the certification object includes a rejection_reason:

Certified data

Where present, certified_data is the user-attested snapshot recorded at certification time.

Read the income figure

Mirror the logic the certification screen uses to pick the income amount:
  1. If certified_data.type is self_employment, use certified_data.monthly_income (gig and bank-detected income).
  2. Otherwise (type: employment), use employments[].pay_rate; if it’s null (typically hourly), fall back to the latest statement’s gross_pay (employments[].statements[0].gross_pay). Always read this amount together with employments[].pay_frequencypay_rate and gross_pay are per-pay-period values, so they’re only meaningful paired with the frequency (W, BW, SM, M, …).
certified_data.monthly_income is populated only for self_employment; for W-2 it’s null, which is why step 2 reads the amount from the report.

Hours per month

certified_data.hours_per_month can be null if it can’t be calculated. Truv calculates it as follows. “Recent pay stubs” below means every pay stub dated in the last 6 weeks with a non-zero hours value. There’s no cap on how many pay stubs that can include. Payroll employees (salaried or hourly): If the employee has a known hourly rate and has recent pay stubs, hours_per_month is the total hours from those pay stubs, extrapolated to a 30-day month. Pay frequency doesn’t matter here, even if it’s known: this check runs first and overrides everything below. Otherwise:
  1. If there are recent pay stubs, average their hours per pay stub, then × pay frequency ÷ 12 if pay frequency is known. If frequency isn’t known, use the total recent-pay-stub hours extrapolated to a 30-day month instead.
  2. If there are no recent pay stubs but the hourly rate is known: monthly income ÷ hourly rate.
  3. If only a pay rate is known (no recent pay stubs or hourly rate): assume a standard full-time month, 174 hours (40 hrs/week × 52 weeks ÷ 12 = 173.33, rounded up).
  4. If monthly income is known: monthly income ÷ federal minimum wage ($7.25/hour), rounded up.
  5. Otherwise: null.
Gig workers: the same recent-pay-stub extrapolation as step 1 above, with no pay-frequency or standard-hours fallback. null if there are no recent pay stubs with hours. Volunteers:
  1. If they have pay stubs older than the last 30 days: sum of hours reported within the last 30 days.
  2. If not, and they’ve been active more than 30 days: total logged hours since they started, pro-rated to 30 days (e.g., 60 hours over 60 days = 30 hours/month).
  3. Otherwise (active 30 days or less, or no start date on file): total logged hours, as-is.
Bank-detected income (no pay stub, just deposits identified as income): monthly income ÷ federal minimum wage ($7.25/hour), rounded up. null if there’s no income amount to estimate from.

Next steps

Follow-up

Re-engage users who didn’t finish verification

Embedded Orders

Full embedded orders implementation guide