AWS EventBridge
Set Convoy as a trigger in your AWS account: when a cargo completes,
Convoy publishes an event onto your EventBridge event bus, and your
rules fan it out to anything — Lambda, Step Functions, SQS, SNS, ECS, API
destinations. Convoy owns one PutEvents call; everything downstream is
yours.
your code ── POST /cargo/load ──▶ Convoy ──▶ …batch processing…
│
▼ cargo completes
AssumeRole(your role, ExternalId)
│
▼
events:PutEvents(Source: "convoy.cargo",
DetailType: "Cargo Completed" | "Cargo Failed",
Detail: <result envelope>)
│
▼
your rules on the bus match & route to N targets
{ "source": ["convoy.cargo"], "detail-type": ["Cargo Completed"] }Why EventBridge is the best “trigger” story of the direct-AWS targets:
- Fan-out and filtering are yours — one cargo completion can trigger N
targets, filtered on any envelope field via
detail.…content patterns (including your owndetail.metadata.…keys, since Convoy echoes your submit-time metadata verbatim). - Retries and DLQs are yours — rule-target delivery retries and dead-letter queues are native EventBridge features.
- Loosely coupled — adding a new consumer needs zero Convoy-side change: just another rule.
Event shape
| Field | Value |
|---|---|
source | convoy.cargo (self-hosted deployments may override via the worker’s CONVOY_EVENTBRIDGE_SOURCE env) |
detail-type | Cargo Completed on success, Cargo Failed on failure |
detail | the standard result envelope |
Success and failure are split across detail-type so rules can match
failures without content filters.
Setup
Create an AWS connection
Same connection as the other direct-AWS targets — see AWS connections. One connection can carry every capability.
Grant events:PutEvents on your role
Scope it to your exact bus — never Resource: "*":
{
"Effect": "Allow",
"Action": "events:PutEvents",
"Resource": "arn:aws:events:us-east-1:123456789012:event-bus/my-bus"
}Or use the CloudFormation role template
with the EventBusArns parameter. Each delivery’s STS session additionally
carries a session policy pinned to the ONE bus the cargo named.
Verify the capability (optional)
In the dashboard, open your AWS connection and run the EventBridge capability check with your event bus ARN. EventBridge has no dry-run mode, so the probe publishes one real marked event to your bus.
The probe event has source: "convoy.verify" and detail-type: "Convoy Verification" — your rules on source = convoy.cargo won’t match it. The
probe is rate-capped at 1/minute per connection, and it’s opt-in: you can
skip it and rely on fail-at-delivery instead.
Submit cargo with an aws_eventbridge callback
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_eventbridge",
"connection_id": "awsconn_…",
"event_bus_arn": "arn:aws:events:us-east-1:123456789012:event-bus/my-bus"
},
"metadata": {"tenant": "acme", "job": "digest-42"}
}'The bus must be in the connection’s region. When the connection carries a target allow-list, the bus ARN must be listed on it.
Complete example: bus + rule + Lambda + DLQ
A minimal SAM/CloudFormation snippet showing the customer-side pieces — a bus, a rule matching Convoy’s completions, a Lambda target, and a DLQ for failed rule deliveries:
Resources:
CargoBus:
Type: AWS::Events::EventBus
Properties:
Name: my-bus
CargoCompletedRule:
Type: AWS::Events::Rule
Properties:
EventBusName: !Ref CargoBus
EventPattern:
source: ["convoy.cargo"]
detail-type: ["Cargo Completed"]
Targets:
- Id: handler
Arn: !GetAtt OnCargoDone.Arn
DeadLetterConfig:
Arn: !GetAtt RuleDLQ.Arn
RetryPolicy:
MaximumRetryAttempts: 8
MaximumEventAgeInSeconds: 3600
# Route failures to a second consumer without touching Convoy.
# FailureTopic must be an EXISTING SNS topic ARN you own — declare it
# elsewhere (or add an AWS::SNS::Topic resource here) and reference it:
CargoFailedRule:
Type: AWS::Events::Rule
Properties:
EventBusName: !Ref CargoBus
EventPattern:
source: ["convoy.cargo"]
detail-type: ["Cargo Failed"]
Targets:
- Id: alerts
Arn: !Ref FailureTopic
RuleDLQ:
Type: AWS::SQS::Queue
OnCargoDone:
Type: AWS::Serverless::Function
Properties:
Handler: handler.lambda_handler
Runtime: python3.12Route on your own metadata with a content filter:
{
"source": ["convoy.cargo"],
"detail-type": ["Cargo Completed"],
"detail": { "metadata": { "tenant": ["acme"] } }
}The result envelope
Your rule targets receive the standard envelope in detail:
| Field | Description |
|---|---|
cargo_id | The cargo this result belongs to — dedupe key |
success | true / false (also reflected in detail-type) |
response | Full model response (or null when truncated/failed) |
response_truncated | true when the response was too large to inline |
result_url | GET /cargo/{cargo_id}/result — fetch the full result |
error_message | Present on failure events only |
metadata | Your submit-time metadata, echoed verbatim |
Delivery is at-least-once: a rare lost response inside Convoy’s retry
window can produce a duplicate event. Dedupe on detail.cargo_id in your
consumers — idiomatic for EventBridge consumers anyway.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
region_mismatch (422 at submit) | The bus is in a different region than the connection. Create a connection in the bus’s region. |
target_not_allowed (422 at submit) | The connection carries a target allow-list and this bus ARN isn’t on it. |
target_not_found in the DLQ | The bus was deleted after submit. |
assume_role_denied + connection flagged failed | The role trust policy or the events:PutEvents grant was removed. Fix the role and re-verify. |
| Events published but nothing fires | Check your rule’s EventBusName and pattern — remember source is convoy.cargo and detail-type splits Completed/Failed. |