Task Lifecycle
Every WorkFunder task follows a well-defined state machine from creation through completion (or cancellation). Understanding this lifecycle is essential for building reliable integrations.
State Machine Diagram
┌──────────┐
│ pending │
│(created) │
└────┬─────┘
│
┌────────────┼────────────┐
│ payment │ │ payment failed
│ succeeded │ │ or cancel
▼ │ ▼
┌──────────┐ │ ┌───────────┐
│ funded │ │ │ cancelled │
│(escrowed) │ │ └─────┬─────┘
└────┬─────┘ │ │
│ │ ▼
worker │ │ ┌──────────┐
accepts │ │ │ refunded │
▼ │ └──────────┘
┌──────────┐ │
│ assigned │◄────┘
└────┬─────┘
│
worker │
checks in │
▼
┌─────────────┐
│ in_progress │
└──────┬ ──────┘
│
worker │
submits proof │
▼
┌────────────────┐
│proof_submitted │
└───────┬────────┘
│
┌────────┴────────┐
proof │ │ proof
approved │ rejected
│ │
▼ ▼
┌──────────┐ ┌───────────────┐
│completed │ │proof_rejected │──── retry ─ ─▶ in_progress
│ (paid) │ └───────┬───────┘ │
└──────────┘ │ │
max attempts submit proof
exhausted │ │
▼ ▼
┌──────────┐ proof_submitted
│ refunded │
└──────────┘
─── At any point after funding ───
┌──────────┐ Either party can dispute
│ disputed │◄─── (within allowed window)
└────┬─────┘
│
admin resolves
│
┌────┴──────────────┐
│ │
▼ ▼
refunded completed
(dev wins) (worker wins)
Task States
pending
The task has been created but not yet funded. The developer needs to complete payment via Stripe.
- Entry:
POST /v1/taskscreates a task in this state - Duration: Until payment is confirmed or task is cancelled
- In test mode: Tasks auto-advance to
funded
funded
Payment has been confirmed. The budget is held in Stripe escrow. Workers are guaranteed payment if they complete the task correctly.
- Entry: Stripe
payment_intent.succeededwebhook received - What happens: Task becomes visible to nearby qualified workers
- Funds: Held in Stripe escrow -- not yet transferred to anyone
posted
The funded task has been posted to the worker marketplace and is visible to qualified workers in the area.
- Entry: Automatic after funding (or manual posting step)
- Visibility: Workers matching the location and skills can see this task
- Exit: A worker accepts, or the task expires/is cancelled
assigned
A verified worker has accepted the task. The worker is now responsible for completing it.
- Entry: Worker accepts via the portal
- Conditions: Worker must have
kyc_status: verifiedandpayout_enabled: true - Cancellation: Developer can cancel at this stage with a 50% refund (worker is compensated for their time)
in_progress
The worker has checked in at the task location. GPS coordinates confirm they are within 500 meters.
- Entry: Worker checks in via the portal (GPS validated)
- Cancellation: Cannot be cancelled by the developer at this stage
- Duration: Until the worker submits proof
proof_submitted
The worker has submitted proof of completion (photo, video, or signature with GPS coordinates).
- Entry: Worker uploads proof via the portal
- Next: Proof enters the review queue for approval or rejection
- Disputes: Either party can open a dispute from this state
completed
The proof has been approved and the worker payout has been initiated via Stripe Transfer.
- Entry: Proof is approved
- Payment: Worker receives
worker_payout_centsin their Stripe Express account - Platform:
platform_fee_centsis captured as the application fee - Disputes: Developer can dispute within 48 hours
proof_rejected
The submitted proof was rejected. The worker can resubmit if they have remaining attempts (max 3).
- Entry: Proof is rejected with a reason
- Next: Worker resubmits (back to
in_progress) or attempts exhausted (torefunded) - Worker notification: Rejection reason is sent via email and push notification
cancelled
The task has been cancelled by the developer.
- Entry: Developer calls
POST /v1/tasks/:id/cancel - Refund: Depends on when cancelled (see Cancellation Refunds below)
- Next: Moves to
refundedwhen Stripe processes the refund
expired
The task was posted but no worker accepted it before the deadline.
- Entry: Automatic when deadline passes without assignment
- Next: Moves to
refunded
disputed
A dispute has been opened by either the developer or the worker.
- Entry: Either party opens a dispute within the allowed window
- Evidence: Both parties have 48 hours to submit evidence
- Resolution: Admin reviews and resolves (see Disputes)
refunded
The developer has been refunded. This is a terminal state.
- Entry: After cancellation, expiration, exhausted proof attempts, or dispute resolved for developer
- Terminal: No further transitions possible
Valid Transitions
| From | To | Trigger | Who |
|---|---|---|---|
pending | funded | Payment confirmed | System (Stripe webhook) |
pending | cancelled | Developer cancels | Developer (API) |
funded | posted | Task posted to marketplace | System (automatic) |
funded | cancelled | Developer cancels | Developer (API) |
funded | refunded | Developer cancels + refund | System |
posted | assigned | Worker accepts | Worker (portal) |
posted | cancelled | Developer cancels | Developer (API) |
posted | expired | Deadline passes | System (automatic) |
assigned | in_progress | Worker checks in (GPS) | Worker (portal) |
assigned | posted | Worker unassigns | Worker (portal) |
assigned | cancelled | Developer cancels (50% refund) | Developer (API) |
in_progress | proof_submitted | Worker submits proof | Worker (portal) |
in_progress | cancelled | Developer cancels | Developer (API) |
proof_submitted | completed | Proof approved | Admin / System |
proof_submitted | proof_rejected | Proof rejected | Admin / System |
proof_submitted | disputed | Dispute opened | Developer or Worker |
completed | disputed | Dispute opened (within 48h) | Developer or Worker |
proof_rejected | in_progress | Worker retries (attempts remaining) | Worker (portal) |
proof_rejected | cancelled | Developer cancels | Developer (API) |
proof_rejected | disputed | Dispute opened | Developer or Worker |
cancelled | refunded | Refund processed | System (Stripe) |
expired | refunded | Refund processed | System (Stripe) |
disputed | refunded | Resolved for developer | Admin |
disputed | completed | Resolved for worker | Admin |
Cancellation Refunds
The refund amount depends on when the task is cancelled:
| Cancelled At Status | Refund to Developer | Worker Compensation |
|---|---|---|
pending | No charge was made | None |
funded | 100% refund | None |
posted | 100% refund | None |
assigned | 50% refund | 50% of budget |
in_progress | Not cancellable | -- |
When a task in assigned status is cancelled, the worker receives 50% of the budget as compensation for their time. This is clearly communicated in the API response when a task transitions to assigned.
Webhooks for State Changes
Every state transition triggers a webhook event. Subscribe to these events to react to task progress in real time:
| State Change | Webhook Event |
|---|---|
-> funded | task.funded |
-> posted | task.posted |
-> assigned | task.assigned |
-> in_progress | task.started |
-> proof_submitted | task.proof_submitted |
-> completed | task.completed |
-> proof_rejected | task.proof_rejected |
-> cancelled | task.cancelled |
-> expired | task.expired |
-> disputed | task.disputed |
-> refunded | task.refunded |
See Webhook Events for full payload formats.
Polling vs. Webhooks
While you can poll GET /v1/tasks/:id to check task status, webhooks are strongly recommended:
| Approach | Pros | Cons |
|---|---|---|
| Polling | Simple to implement | Uses API rate limit, delayed updates |
| Webhooks | Real-time, no rate limit impact | Requires a public endpoint |
For AI agent workflows where maintaining a webhook endpoint is difficult, polling every 30-60 seconds is a reasonable fallback.