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
-
Authenticate.
JWTAuthorizervalidatesx-session-tokenagainst the Payability auth API and resolveslogin_id,login_key,sourceand optionalsupplier_key. -
Authorize. Internal callers must hold
DEV,FINANCEorINT_SUPER_USER; supplier-dashboard callers skip the perm check. -
Resolve the process.
ProcessFactory.identify_process_nameaccepts either a process name or a task-step name. -
Guardrails. Supplier-dashboard requests must target their own
supplier_key; an identical non-failed in-flight execution short-circuits to the existingcorrelation_id; the supplier dashboard is capped at 3 executions per supplier + process;force_new_executionbypasses dedup for internal callers only. -
Source allow-list. The task’s
get_available_process_sources()decides whichProcessSourcevalues may start it. -
Run the request task.
ProcessFactory.build()returns both steps, and the handler executes only the first. That step persists the row, sets the toolPENDING, validates input if the process has a validator, and starts the SWF workflow for step two. -
Handle step. The worker builds the task through
TaskFactory, reloads the input (bycorrelation_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) providescheck_permissions(), theStopExecutioncontrol-flow exception for an early controlled exit,build_json_response()(a{code, status, message}envelope) and the metrics namespace. -
WebRequestLoggerlogs[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):
-
Long-poll
poll_for_activity_taskonautomatic-uw-tool-task-list(read timeouts and closed connections simply retry). -
Parse
activity_task['input']into a task definition, build the task viaTaskFactory, deserializetask_input, andexecute()it. -
On success:
RespondActivityTaskCompletedwithoutput.to_dict(), plus a{process_name}_success_time_takenmetric. -
On any exception: capture to Sentry,
RespondActivityTaskFailed(shortreason, full payload indetails), plus a{process_name}_failuremetric.
Because task_failure_handling already marked the task failed and the tool ERROR, an SWF
retry finds consistent state.