Instructions for collection modules

Instructions for collection modules

Instructions are first-class platform entities that let you schedule a collection module function to execute at a specific date and time in the future. They are the preferred scheduling mechanism on the platform — use them for any deferred collections work: scheduled retries outside the standard billing cadence, time-bounded payment-method follow-ups, end-of-term collection cleanup, deferred configuration changes.

Collection-module instructions run on the same platform scheduler as product-module instructions — same cron cadence, statuses, idempotency, and permissions — with a few deliberate differences in how they are created and what the function receives. This guide covers the collection-module implementation; the differences from product modules are called out throughout.

How they work

An instruction stores:

  • The collection module it targets (module_id)
  • Optionally, a policy for context
  • The function name to invoke
  • A module payload — free-form data passed to the function at execution
  • An execution datetime — when to run, must land on a 30-minute boundary (:00 or :30)

The platform runs a cron job every 30 minutes that picks up every pending instruction whose execution_datetime has passed. Each is dispatched via a shared FIFO queue, and the named function is invoked on the collection module's deployed code.

Lifecycle

pending → queued → executed → successful
                            ↘ failed
       ↘ cancelled
StatusMeaning
pendingCreated. Waiting for the next cron run (every 30 minutes) to pick it up.
queuedCron has dispatched it; execution will follow within seconds.
executedFunction has started running.
successfulFunction returned without throwing. Terminal.
failedFunction threw, the target was unavailable, OR a platform sweep marked it failed (see Failure modes). Terminal.
cancelledManually cancelled before execution. Terminal.

Execution ordering

  • Instructions created with a policy_id serialize per policy (FIFO message group = the policy).
  • Instructions created without a policy_id serialize per collection module (FIFO message group = the module ID) — so policy-less instructions for the same module execute one at a time, in execution_datetime order.
  • Instructions in different groups execute in parallel, up to the platform's concurrency limit (default 50).

Creating an instruction

Collection-module instructions are created via the API only. Unlike product modules, collection module code cannot return add_instruction (or any instruction action) from its hooks — the collection-module action set is limited to payment scheduling actions (schedule_payment, reschedule_payment, unschedule_payment).

POST /v1/insurance/instructions
Content-Type: application/json
Authorization: Basic <api_key>:

{
  "module_type": "collection_module",
  "module_id": "11111111-2222-3333-4444-555555555555",
  "function_name": "retry_failed_collection",
  "module": {
    "collection_attempt": 2,
    "original_failure_reason": "insufficient_funds"
  },
  "execution_datetime": "2026-08-01T14:00:00.000Z",
  "description": "Retry the failed collection after the grace window",
  "manually_executable": true
}

Request shape — differences from product modules:

FieldCollection module behavior
module_type"collection_module"
module_idRequired. The collection module ID. It is authoritative — validated for ownership at creation and used directly at dispatch.
policy_idOptional. Context only — validated if supplied, and used for execution ordering (see above). The engine does not need it to dispatch.
claim_idNot supported. Rejected with a 400 — collection-module instructions cannot be claim-scoped.

The payment method is deliberately not part of the instruction: it can change between scheduling and execution, so resolve it inside the function at execution time.

Required validation (same as product modules):

  • execution_datetime must be at least 15 minutes in the future
  • execution_datetime must land on a 30-minute boundary — minutes are 00 or 30, seconds/milliseconds zero (e.g. 14:00:00.000Z or 14:30:00.000Z, never 14:15:00.000Z)

Optional fields description, hidden, and manually_executable behave exactly as for product modules (defaults null, false, false).

Response: 201 Created with the new instruction, OR 200 OK with the existing instruction if an exact duplicate already exists (see Idempotency).

The instruction function

At execution time, the platform invokes the named function on the collection module's deployed code with a single argument containing two keys only:

async function retry_failed_collection({
  instruction_data, // the `module` payload supplied at creation
  instruction,      // the full PlatformInstruction row
}) {
  // Resolve current state at execution time — the function does NOT
  // receive policy, policyholder, or claim arguments.
  const attempt = instruction_data.collection_attempt;
  // ... perform the deferred collections work
}

Key differences from product-module instruction functions:

  • No policy, policyholder, or claim arguments. The function receives only instruction_data and instruction. If you need policy or payment-method state, load it inside the function so you always act on current data.
  • The return value is ignored. Collection-module instruction functions are fire-and-forget — there is no action-array processing. Perform your work through the collection module's normal capabilities inside the function body.
  • instruction_data and instruction.module carry the same value — instruction_data is provided as a separate argument for convenience.

Idempotency

Identical to product modules: exact duplicates are deduplicated on (organization, environment, module_id, policy_id, function_name, execution_datetime, module) while pending, with the existing row returned as 200. JSON key order in module is normalised. Two policy-less instructions with otherwise identical fields are treated as exact duplicates. Vary the module payload to force a distinct row at the same slot.

Cancelling and triggering

  • Cancel: POST /v1/insurance/instructions/{instruction_id}/cancel — only pending instructions can be cancelled; cancelled rows don't block re-creation. Same as product modules.
  • Manual trigger: POST /v1/insurance/instructions/{instruction_id}/trigger — available for instructions created with manually_executable: true; fires synchronously before the scheduled datetime. Same as product modules.
  • Bulk cancel is not available. The bulk_cancel_instructions action is a product-module-code action and there is no HTTP equivalent — cancel collection-module instructions one at a time via the /cancel endpoint.

Failure modes

The generic failure reasons apply exactly as for product modules — stale (more than 24 hours overdue at pickup), stuck_in_queued and stuck_in_executed (sweep timeouts after 30 minutes), and function_not_found (the named function is not defined in the deployed collection module code).

Collection-module specifics:

ScenarioResult
The function threw an exceptionfailed with failure_reason: null. Details are in the collection module logs.
The named function doesn't exist in the deployed codefailed with failure_reason: function_not_found.
The target is unavailable — collection module deleted, no live definition, or no deployed codefailed with failure_reason: null. Deliberately terminal (retrying would never succeed).
Transient infrastructure error (Lambda/transport failure, 5xx, timeout)Reverted to queued and retried via SQS redelivery; only exhausted redeliveries end in stuck_in_queued.

Note the observability difference: collection-module instruction runs do not produce a product-module run-log row (product_module_code_run_id stays null). To investigate a failure, look in the collection module logs.

failed instructions are not retried automatically. To re-execute, create a new instruction.

Listing instructions

The same generic list/count surface applies — scope to collection modules with module_type:

GET  /v1/insurance/instructions?module_type=collection_module
GET  /v1/insurance/instructions?module_type=collection_module&module_id={collection_module_id}&status=pending,failed
HEAD /v1/insurance/instructions?module_type=collection_module     # returns just the total-count header

All standard filters, pagination, single fetch (GET /instructions/{instruction_id}), and the dashboard aggregate endpoints (/instructions-stats, /instructions-status-counts) work for collection-module instructions — see the product-module instructions guide for the full reference.

Permissions

Identical to product-module instructions: instructions:read, instructions:read_hidden, instructions:create, instructions:trigger, instructions:cancel.

Operational guidance

  • Resolve state at execution time. Because the function receives no policy or payment-method arguments, and because payment methods can change between scheduling and firing, always load current state inside the function.
  • Keep the module payload small and schema-validate it inside the function — the platform doesn't validate its shape.
  • Spread large batches across slots. As with product modules, avoid concentrating thousands of instructions on a single 30-minute boundary.
  • Defensive re-creation is safe — exact duplicates are idempotent no-ops.

Related guides


Did this page help you?