Skip to Content
IntegrationsAWS Lambda Invoke

AWS Lambda Invoke

Have Convoy invoke your Lambda function with the result when a cargo completes — no webhook endpoint, no polling. From your side, the function simply “fires when the cargo is done”, receiving the standard result envelope as its event.

your code ── POST /cargo/load ──▶ Convoy ──▶ …batch processing… │ ▼ cargo completes AssumeRole(your role, ExternalId) │ ▼ lambda:InvokeFunction(InvocationType='Event', Payload=<result envelope>) │ ▼ your function runs with the result

Convoy assumes the IAM role from your AWS connection and calls lambda:InvokeFunction asynchronously (InvocationType='Event'). Convoy’s delivery responsibility ends at the 202 from the Lambda front-end — after that, Lambda’s own async invoke machinery owns execution: its per-function retries (2 by default) and your configured on-failure destination  or DLQ.

This differs from the token-based targets (durable callbacks and Step Functions task tokens): there is no suspended wait to resume. It’s the right fit when you want an event-driven handler for results rather than a paused workflow.

Setup

Create an AWS connection

Same connection as the other direct-AWS targets — see AWS connections. One connection can carry every capability.

When you create the connection, list the function(s) Convoy may invoke in allowed_function_arns. For aws_lambda_invoke this list is enforced at submit time: a non-null list rejects any function_arn not on it.

Grant lambda:InvokeFunction on your role

Add an invoke statement to the connection’s IAM role, scoped to your exact function(s) — never Resource: "*":

{ "Effect": "Allow", "Action": "lambda:InvokeFunction", "Resource": [ "arn:aws:lambda:us-east-1:123456789012:function:on-cargo-done", "arn:aws:lambda:us-east-1:123456789012:function:on-cargo-done:*" ] }

Or use the CloudFormation role template  with the InvokeFunctionArns parameter.

Convoy defends in depth: each delivery’s STS session carries a session policy scoped to the ONE function the cargo named, so even a broader role policy can’t be used to invoke anything else.

Verify the capability

In the dashboard, open your AWS connection and run the Lambda invoke capability check with your function’s qualified ARN (e.g. arn:aws:lambda:us-east-1:123456789012:function:on-cargo-done:$LATEST). The probe DryRun-invokes your function — it validates the permission without executing it.

Submit cargo with the invoke callback

The function_arn must be a qualified ARN — append :$LATEST, a version number, or an alias. Unqualified ARNs are rejected at submit (durable functions cannot be invoked with an unqualified ARN).

curl -X POST https://api.cnvy.ai/cargo/load \ -H "X-API-Key: $CONVOY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "params": { "model": "claude-haiku-4-5", "max_tokens": 1024, "messages": [{"role": "user", "content": "Summarize this document…"}] }, "callback": { "type": "aws_lambda_invoke", "connection_id": "awsconn_…", "function_arn": "arn:aws:lambda:us-east-1:123456789012:function:on-cargo-done:$LATEST" } }'

Submit-time validation: the ARN must be qualified (version, alias, or $LATEST), must match the connection’s region (region_mismatch otherwise), and — when the connection has an allowed_function_arns list — must be on it (target_not_allowed otherwise). A stored unqualified ARN on the allow-list also matches qualified submits of the same function (…:function:name allows …:function:name:prod).

The handler

Your function receives the result envelope directly as event:

def handler(event, context): if event["success"]: response = event["response"] if response is None and event["response_truncated"]: # Oversize result — fetch from the hosted results mailbox response = fetch(event["result_url"]) process(response) else: # Failure cargo delivers through the SAME invoke, with # success=false and an error_message — there is no separate # failure API for this target. handle_failure(event["cargo_id"], event["error_message"])

Envelope fields:

FieldNotes
cargo_idThe cargo identifier — dedupe key (see below)
successtrue/false
responseFull model response (null on failure or when truncated)
response_truncatedtrue when the response exceeded the 240 KB inline budget
result_urlGET /cargo/{cargo_id}/result — always present
error_messagePresent only on failure envelopes
metadataYour opaque /cargo/load metadata, echoed verbatim

Async invoke accepts payloads up to 256 KB; Convoy’s 240 KB inline budget keeps every envelope under it — oversize responses ship the result_url pointer instead.

At-least-once: dedupe on cargo_id

InvokeFunction(Event) is not idempotent. If Convoy’s delivery times out after Lambda accepted the invoke but before the 202 arrived, the delivery retries and your function may run twice (Lambda’s own async retries can also re-run it). If duplicate processing matters, dedupe on cargo_id — one DynamoDB conditional put:

import boto3 from botocore.exceptions import ClientError table = boto3.resource("dynamodb").Table("processed-cargo") def handler(event, context): try: table.put_item( Item={"cargo_id": event["cargo_id"]}, ConditionExpression="attribute_not_exists(cargo_id)", ) except ClientError as e: if e.response["Error"]["Code"] == "ConditionalCheckFailedException": return # already processed — duplicate delivery raise process(event)

Configure an on-failure destination. A 202 means Lambda accepted the invoke — if your function then crashes on every retry, Convoy considers the cargo delivered (that’s the async-invoke contract). Set an OnFailure destination (SQS/SNS/EventBridge) on the function so failed executions land somewhere you can see. The result_url in the envelope means the result itself is never lost — it stays retrievable from the hosted results mailbox for the retention period.

Delivery semantics

  • Both success and failure cargo invoke your function — branch on event["success"].
  • Retries: transient AWS errors (throttles, 5xx) retry up to 5 times with exponential backoff (1, 3, 9, then 27 minutes between attempts — roughly 40 minutes end to end). Terminal conditions (function deleted, invoke grant removed, broken function KMS key) stop immediately and mark the cargo callback_failed.
  • Recovery: the result stays retrievable via GET /cargo/{cargo_id}/result regardless of delivery outcome.

Troubleshooting

SymptomCauseFix
422 region_mismatch at submitfunction_arn region ≠ connection regionUse a connection registered for the function’s region
422 unqualified ARN at submitfunction_arn missing :$LATEST/version/aliasUse a qualified function ARN
422 target_not_allowed at submitARN not on allowed_function_arnsAdd it to the connection’s allow-list (or clear the list)
target_not_found in delivery logsFunction deleted/renamed since submitRedeploy the function or resend with the new ARN
assume_role_deniedTrust policy or invoke grant removedFix the role, re-verify the connection
target_misconfigured (KMS…)Function’s env-var KMS key brokenFix the key customer-side; resend
Function ran twiceAt-least-once deliveryDedupe on cargo_id (above)
Function never ran but cargo shows deliveredFunction crashed after the 202Check the function’s own logs + OnFailure destination
Last updated on