Early Wage Access (EWA)

Slate Early Wage Access lets your workers access wages they have already earned before payday. Unlike Capital and Credit Lines, it is partner-initiated: there is no pre-approval and no offer to present. When a worker requests an advance, you create a finance request for that specific amount (a shift, a day, a pay period), the worker completes a short application, and on approval Slate creates an ewa financing agreement, disburses the funds, and collects a single repayment, the advanced amount plus a fixed service fee, on the repayment date.

EWA is built for high frequency: a customer can have several finance requests active at the same time, each one fully independent, with its own status, application, agreement and webhook stream. One shift, one finance request.

This guide walks the full flow end to end: which endpoints to call, which webhooks to listen to, what to embed, and what your platform is expected to do at each step.

All amounts in the EWA API are integer cents (e.g. 50000 = $500.00). All API requests are authenticated with your private API key in the x-api-key header. Webhooks are delivered and signed, see Webhooks for signature verification.

EWA works with person customers only, and it is the only product that uses finance requests. Pre-approvals belong to Capital and Credit Lines; both can coexist with EWA for the same integration without interacting.


Lifecycle at a glance

You create the customer (PERSON) ──► You create a finance request ──► CREATED
                                        webhook: finance-request.created

                                                     │  you show the application form

                              Customer opens the application ──► IN_PROGRESS
                                        webhook: finance-request.in-progress


                              Customer completes and submits ──► SUBMITTED
                                        webhook: finance-request.submitted
                                                     │  Slate underwrites
                                      ┌──────────────┴──────────────┐
                                      ▼                             ▼
                                  APPROVED                      DECLINED
                        webhook: finance-request.approved   webhook: finance-request.declined
                        financingAgreementId populated          the flow ends


                        Financing agreement (type: ewa)
                        webhook: financing-agreement.created


                        Disbursement ──► webhook: financing-agreement.disbursed


                        Repayment on the repayment date ──► webhook: financing-agreement.repayment


                                  COMPLETED
                        webhook: financing-agreement.completed

A finance request can also end in CANCELED (you cancel it) or EXPIRED (7 days without completion) — see Cancellation and Expiration.

Key facts:

  • You initiate every advance. There is no pre-approval and no banner; you create a finance request when the worker asks for their pay early.
  • Person customers only. EWA agreements are executed with private individuals, the workers on your platform.
  • One repayment, one fee. The customer repays the advanced amount plus a fixed serviceFee on the agreement's repaymentDate — no installments, no factor rate.
  • Requests are independent and concurrent. Create as many as the use case needs; each has its own lifecycle.
  • Requests expire. A request not completed within 7 days expires automatically.

Step 1 — Create the customer

Create the worker as a PERSON customer with your own stable identifier (externalId). If the customer already exists, skip this step.

curl -X POST "$BASE_URL/customers" \
  -H "x-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "PERSON",
    "externalId": "cus_456",
    "person": {
      "firstName": "John",
      "lastName": "Doe",
      "email": "john.doe@email.com",
      "phone": "+15551234567"
    }
  }'

All detail fields are optional at creation — whatever you don't provide, the customer fills in during their first application. The more you provide upfront, the shorter that application.

Webhook — customer.created confirms the customer exists; data.id is the customer id.

Step 2 — Create the finance request

When the worker requests an advance, create a finance request for the exact amount:

curl -X POST "$BASE_URL/finance-requests" \
  -H "x-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "0195b7d2-...",
    "amount": 50000,
    "currency": "CAD",
    "type": "ewa",
    "externalId": "shift_001",
    "metadata": {
      "shiftDate": "2026-07-18",
      "rate": "67",
      "hours": "3.4"
    }
  }'

Response

{
  "id": "019f8186-7021-73ea-a169-b12cd581c38c",
  "customerId": "0195b7d2-...",
  "externalId": "shift_001",
  "status": "CREATED",
  "type": "ewa",
  "amount": 50000,
  "currency": "CAD",
  "metadata": {
    "shiftDate": "2026-07-18",
    "rate": "67",
    "hours": "3.4"
  },
  "financingAgreementId": null,
  "createdAt": "2026-07-19T10:00:00.000Z",
  "updatedAt": "2026-07-19T10:00:00.000Z",
  "expiresAt": "2026-07-26T10:00:00.000Z"
}

Webhook — finance-request.created fires; data.id is the finance request id.

externalId is your reference for the underlying event (the shift, the pay period). It's how you reconcile requests with your own records.

Metadata — don't skip it

The metadata field accepts an arbitrary JSON object and stays attached to the request for its whole lifecycle. Always send it: it carries the partner-side context behind the amount you're requesting — the evidence that the wages were actually earned — and Slate's underwriting relies on it to decide the request. A request without metadata is a request Slate has to evaluate blind.

For a shift-based advance, that means at minimum:

{
  "shiftDate": "2026-07-18",
  "rate": "67",
  "hours": "3.4"
}

The exact fields are agreed with Slate for your integration. Metadata is set at creation and cannot be updated — if you need to change it, cancel the request and create a new one.

Finance request statuses

Status Meaning
CREATED Request created; the customer hasn't started yet
IN_PROGRESS The customer opened the application form
SUBMITTED The application was submitted; under review
APPROVED Approved — financingAgreementId is populated
DECLINED Reviewed and declined
CANCELED You canceled the request before approval
EXPIRED Not completed within 7 days

APPROVED, DECLINED, CANCELED and EXPIRED are terminal — no further transitions happen.

Step 3 — Open the application

Mint a short-lived session token server-side for the logged-in worker:

curl -X POST "$BASE_URL/user-session-token" \
  -H "x-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "externalId": "cus_456" }'
{ "token": "eyJhbGciOi..." }

Never expose your API key to the browser — only the session token. Then embed the application form, passing the finance request id:

<script async type="module" src="https://components.tryslatehq.com/slate.esm.js"></script>

<slate-application-form
  env="live"
  user-token="<session-token>"
  finance-request-id="019f8186-7021-73ea-a169-b12cd581c38c"
></slate-application-form>

finance-request-id is required for EWA. Each request is independent and there is no "latest request" fallback — the form loads the application tied to that specific request. Without the attribute, the form loads the customer's pre-approval application instead (the Capital flow).

See the Application Form component page for all its properties, events and caveats, and The Application Flow for the screens the customer goes through. Any information you already provided when creating the customer is not asked again — after the first advance, subsequent applications are very short.

Step 4 — The customer applies

As the worker moves through the application, the finance request tracks it:

Webhook Status change When
finance-request.in-progress CREATED → IN_PROGRESS The customer opened the application form
finance-request.submitted IN_PROGRESS → SUBMITTED The customer completed and submitted it

Check the current state any time:

curl "$BASE_URL/finance-requests/{id}" \
  -H "x-api-key: $API_KEY"

Step 5 — Decision

Slate underwrites the submitted request and decides:

  • Declinedwebhook finance-request.declined; the flow ends and no agreement is created.
  • Approvedwebhook finance-request.approved; the request's financingAgreementId is now populated, and you also receive financing-agreement.created with the agreement id.

Step 6 — The financing agreement

Fetch the executed agreement:

curl "$BASE_URL/v2/financing-agreements/{financingAgreementId}" \
  -H "x-api-key: $API_KEY"
{
  "id": "01a0d411-...",
  "customerId": "0195b7d2-...",
  "type": "ewa",
  "ewa": {
    "serviceFee": 2000,
    "repaymentDate": "2026-08-01"
  },
  "status": "IN_PROGRESS",
  "fundedAmount": 50000,
  "currency": "CAD",
  "fundedDate": "2026-07-19",
  "firstPaymentDate": "2026-08-01",
  "estimatedLastPaymentDate": "2026-08-01",
  "disbursed": false,
  "remainingBalance": 52000,
  "totalPaid": 0,
  "payments": [],
  "upcomingPayments": [
    { "amount": 52000, "currency": "CAD", "date": "2026-08-01T00:00:00Z" }
  ],
  "createdAt": "2026-07-19T14:03:11.000Z",
  "updatedAt": "2026-07-19T14:03:11.000Z"
}

Reading the terms: the worker received fundedAmount ($500.00) and repays fundedAmount + ewa.serviceFee ($520.00) in a single collection on ewa.repaymentDate. That's the whole cost — no factor rate, no interest, no installments.

Step 7 — Disbursement

Slate transfers the funds to the worker's verified bank account:

Webhook — financing-agreement.disbursed

{
  "event_type": "financing-agreement.disbursed",
  "data": { "id": "01a0d411-..." },
  "timestamp": "2026-07-19T15:00:00Z"
}

The agreement's disbursed field flips to true.

Step 8 — Repayment and closure

On the repayment date, Slate debits the full amount (advance + service fee) from the worker's bank account via PAD. There is no endpoint to record payments — you only monitor:

  • financing-agreement.repayment — the collection happened; refetch the agreement for remainingBalance, totalPaid and payments.
  • financing-agreement.completed — fully repaid; status becomes COMPLETED.
  • financing-agreement.defaulted — Slate marked the agreement as defaulted; status becomes DEFAULTED.

Cancellation

You can cancel a finance request at any point before it reaches a terminal state — while it is CREATED, IN_PROGRESS or SUBMITTED:

curl -X POST "$BASE_URL/finance-requests/{id}/cancel" \
  -H "x-api-key: $API_KEY"

What happens:

  • The request transitions to CANCELED and webhook finance-request.canceled fires.
  • If the customer has an application in progress (even if already submitted), it is automatically cancelled — they cannot continue it.
  • No financing agreement is created.

Common reasons to cancel: the underlying event is no longer valid (the shift was cancelled), the customer took too long and you want to close the request early, or you need different terms — cancel and create a fresh request.

Once a request is APPROVED or DECLINED it cannot be canceled — those are terminal states.

Expiration

Requests in CREATED or IN_PROGRESS that haven't progressed within 7 days of creation expire automatically: webhook finance-request.expired fires and the request becomes terminal. The expiresAt field on every request tells you the deadline. Requests in any other status are never expired.

Multiple concurrent requests

A customer can have any number of active finance requests at once — this is the intended pattern for EWA, where each shift produces its own request:

Customer: cus_456
  ├── shift_001 → APPROVED
  ├── shift_002 → IN_PROGRESS
  └── shift_003 → CREATED

Each request is fully independent: its own status, its own application, its own agreement, its own webhooks. When embedding the application form, always pass the finance-request-id of the request the worker is acting on.


Monitoring endpoints

Endpoint What it returns
POST /finance-requests Creates a finance request
GET /finance-requests Paginated requests. Filters: customerId, externalId, status
GET /finance-requests/{id} One request with its current status and financingAgreementId
POST /finance-requests/{id}/cancel Cancels a non-terminal request
GET /v2/financing-agreements?type=ewa Paginated EWA agreements. Filters: externalId, status
GET /v2/financing-agreements/{id} One agreement with ewa.serviceFee, ewa.repaymentDate, payments, upcomingPayments, remainingBalance, totalPaid

Webhooks summary

Subscribe to these events (see Webhooks for delivery and signature verification). Every payload has the same shape: { event_type, data: { id }, timestamp }.

Event data.id refers to When it fires What you should do
customer.created Customer The customer was created Store the customer id
finance-request.created Finance request You created a request Store the request id
finance-request.in-progress Finance request The customer opened the application Track engagement
finance-request.submitted Finance request The application was submitted Show an "under review" state
finance-request.approved Finance request The request was approved Fetch it — financingAgreementId is populated
finance-request.declined Finance request The request was declined Reflect it to the worker
finance-request.canceled Finance request You canceled the request Confirmation only
finance-request.expired Finance request 7 days passed without completion Create a new request if the worker still wants the advance
financing-agreement.created Financing agreement The executed agreement exists Store the agreement id
financing-agreement.disbursed Financing agreement Funds were sent to the worker Reflect it to the worker
financing-agreement.repayment Financing agreement The repayment was collected Refetch the agreement
financing-agreement.completed Financing agreement Fully repaid Reflect it in your UI
financing-agreement.defaulted Financing agreement The agreement defaulted Reflect it in your UI

Good practices

  • One request per event. Model each shift or pay period as its own finance request with its own externalId — don't batch several advances into one.
  • Always send metadata. Shift date, rate and hours are what Slate's underwriting uses to validate the amount you're requesting — a request without them is evaluated blind. Remember it's immutable: cancel and recreate to change it.
  • Cancel stale requests proactively. If the underlying event becomes invalid, cancel right away instead of waiting for the 7-day expiration.
  • Always pass finance-request-id to the application form. Without it, the form loads the pre-approval (Capital) flow, not the EWA request.
  • Treat webhooks as notifications, not payloads. They carry only the object id — always fetch the current state from the API afterward.
  • Don't compute the fee client-side. serviceFee, repaymentDate and balances come from the agreement endpoint — read them from there.