Request lifecycle

Two shapes of execution exist. The process-driven shape covers three of the four processes plus Au10tix identity documents; the webhook-driven shape covers Entrust identity verification.

Process-driven: POST /tasks/execute

sequenceDiagram
    participant C as Client
    participant API as Web API
    participant DDB as DynamoDB
    participant B2B as B2B Task Manager
    participant SWF as AWS SWF
    participant W as SWF Worker
    participant V as Vendor (Au10tix / TR)
    participant EB as EventBridge

    C->>API: POST /tasks/execute {task_name, task_input}
    API->>API: JWT auth, permissions, source scoping, dedup, rate limit
    API->>DDB: TaskExecutionTable.save (status=pending)
    API->>DDB: ToolTable.update (status=PENDING)
    API->>B2B: start_swf(task_name=handle_*, correlation_id, input)
    B2B->>SWF: StartWorkflowExecution
    API-->>C: 200 {correlation_id}

    Note over SWF,W: later, asynchronously
    W->>SWF: poll_for_activity_task(task_list=automatic-uw-tool-task-list)
    SWF-->>W: activity {task_name=handle_*, task_input}
    W->>DDB: load the full input by correlation_id
    W->>V: vendor call (submit, poll readiness, fetch result)
    V-->>W: raw result
    W->>S3: archive raw result JSON
    W->>DDB: task -> processed; FinalStatusTable + ToolTable written
    W->>EB: ProcessFinishedEvent (+ TR match / mismatch events)
    W->>SWF: RespondActivityTaskCompleted

The caller then polls GET /tasks/{correlation_id} for status and GET /tasks/result for the outcome.

Step by step

  1. Authenticate. JWTAuthorizer validates x-session-token against the Payability auth API and resolves login_id, login_key, source and optional supplier_key.

  2. Authorize. Internal callers must hold DEV, FINANCE or INT_SUPER_USER; supplier-dashboard callers skip the perm check.

  3. Resolve the process. ProcessFactory.identify_process_name accepts either a process name or a task-step name.

  4. Guardrails. Supplier-dashboard requests must target their own supplier_key; an identical non-failed in-flight execution short-circuits to the existing correlation_id; the supplier dashboard is capped at 3 executions per supplier + process; force_new_execution bypasses dedup for internal callers only.

  5. Source allow-list. The task’s get_available_process_sources() decides which ProcessSource values may start it.

  6. Run the request task. ProcessFactory.build() returns both steps, and the handler executes only the first. That step persists the row, sets the tool PENDING, validates input if the process has a validator, and starts the SWF workflow for step two.

  7. Handle step. The worker builds the task through TaskFactory, reloads the input (by correlation_id, or straight from the SWF payload for Au10tix), performs the vendor calls, archives raw JSON to S3, writes verdicts, and emits events.

Webhook-driven: Entrust identity verification

sequenceDiagram
    participant S as Supplier
    participant API as Web API
    participant ONF as Entrust / Onfido
    participant DDB as DynamoDB
    participant HS as HubSpot
    participant B2B as B2B Task Manager
    participant W as SWF Worker
    participant INT as Payability Internal API

    S->>API: POST /entrust/sessions {supplier_key, person_id}
    API->>INT: get_supplier_info (owner first/last name)
    API->>ONF: create applicant, create workflow run
    ONF-->>API: {id, sdk_token, link:{url, expires_at}}
    API->>DDB: TaskExecutionTable.save (task_id = workflow_run_id, pending)
    API->>DDB: ToolTable.update (ENTRUST, PENDING)
    API->>HS: entrust_verification_status = in_progress (best effort)
    API-->>S: 200 {url, workflow_run_id, sdk_token, link_expires_at}

    S->>ONF: completes the hosted verification flow
    ONF->>API: POST /entrust/webhook (workflow_run.completed, HMAC signed)
    API->>API: verify X-SHA2-Signature, filter event type
    API->>DDB: TaskExecutionTable.get(workflow_run_id)
    API->>B2B: start_swf(entrust/handle_identity_document_validation_response)
    API-->>ONF: 200 {"status": "accepted"}

    W->>ONF: get_workflow_run + hydrate step tasks
    W->>S3: archive full run JSON
    W->>INT: archive stale ID captures, upload documents, then liveness media
    W->>DDB: FinalStatusTable + ToolTable + task processed
    W->>INT: approve uploaded documents (approved verdict only)
    W->>HS: verified / in_review / denied (best effort)
    W->>EB: ProcessFinishedEvent

The webhook stays deliberately thin — verify, correlate, enqueue — because the aftermath (Onfido re-read, S3 archive, document download and re-upload) is far too slow to run inside a webhook request without risking a delivery timeout. Full detail in validate_identity_document (Entrust / Onfido).

Inside an endpoint handler

FastAPI route
  └─ JWTAuthorizer dependency          → 400 / 401 / 404 / 409 / 500 on a bad token
       └─ Handler.handle()
            ├─ WebRequestLogger.log(login_id, source, url, body)
            ├─ check_permissions()      (where applicable)
            ├─ validate input / scope the source
            ├─ business logic (DynamoDB, S3, B2B, vendor client)
            ├─ send_metric()            → CloudWatch via pyb-sdk
            └─ return a pydantic model | JSONResponse
  • BaseEndpointHandler (interface/web_api/endpoint_handling/base.py) provides check_permissions(), the StopExecution control-flow exception for an early controlled exit, build_json_response() (a {code, status, message} envelope) and the metrics namespace.

  • WebRequestLogger logs [login_id:source] /path (payload) at INFO.

  • Several endpoints accept an untyped dict = Body() and validate manually in the handler. New endpoints should use pydantic models instead — see Python standards.

Inside the worker loop

Each of the five threads runs the same loop (swf_app.py):

  1. Long-poll poll_for_activity_task on automatic-uw-tool-task-list (read timeouts and closed connections simply retry).

  2. Parse activity_task['input'] into a task definition, build the task via TaskFactory, deserialize task_input, and execute() it.

  3. On success: RespondActivityTaskCompleted with output.to_dict(), plus a {process_name}_success_time_taken metric.

  4. On any exception: capture to Sentry, RespondActivityTaskFailed (short reason, full payload in details), plus a {process_name}_failure metric.

Because task_failure_handling already marked the task failed and the tool ERROR, an SWF retry finds consistent state.