http_call
Outbound HTTP with templated url/body and optional HMAC signing.
rflow_version: 1
name: trade-hooks
config:
port: 3940
db_connection: ${DATABASE_URL}
networks:
- name: ethereum
chain_id: 1
rpc: ${ETH_RPC}
contracts:
Pool:
abi: ./abis/uniswap-v3-pool.json
network: ethereum
address: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"
secrets:
API_KEY: ${BACKEND_API_KEY}
HOOK_KEY: ${BACKEND_HMAC_KEY}
workflows:
trade-hook:
trigger:
event:
contract: Pool
name: Swap
network: ethereum
steps:
- id: notify-backend
http_call:
url: ${BACKEND_URL}/hooks/trade
method: POST
headers:
x-api-key: "${{ secrets.API_KEY }}"
hmac: "${{ secrets.HOOK_KEY }}"
body:
trader: "${{ trigger.args.sender }}"
amount: "${{ trigger.args.amount0 }}"
src: "${{ trigger.tx_hash }}"Fields
| Field | Required | Description |
|---|---|---|
url | ✅ | Templated request url |
method | Defaults to POST when a body is set, GET otherwise | |
headers | Map of headers; values are templates | |
body | A YAML mapping rendered recursively (every string leaf is a template), then sent as JSON | |
hmac | Secret used to sign the request (see below) |
HMAC signing
When hmac: is set, rflow computes HMAC-SHA256 over the raw request body bytes
(the empty byte string for body-less requests) and sends the lowercase hex digest as
the x-rflow-signature header. Your backend verifies with the shared secret,
typically one from secrets: so it never
appears in logs.
# receiver side (python)
expected = hmac.new(key, request_body_bytes, hashlib.sha256).hexdigest()
assert hmac.compare_digest(expected, request.headers["x-rflow-signature"])Idempotency and recovery
An HTTP request can reach the receiver before rflow records its outcome. A crash or retry in that window can send it again. HMAC proves authenticity; it does not prevent duplicate effects.
For a consequential request, pass a stable key that the receiver atomically deduplicates with its own write. For one request step in a run, for example:
headers:
x-idempotency-key: "${{ run.id }}:notify-backend"Use different keys for different steps and foreach items. If the same business operation must remain deduplicated across newly created runs, live replays or a database restore, use its stable business identity instead of a run UUID. Match the receiver's key-retention period to your retry and recovery requirements.
Dry-run logs the prepared native request without sending it. This does not
sandbox a separate command: step that makes its own HTTP calls.
Output and failure mapping
A 2xx response succeeds with output:
{ "status": 200, "body": { ... } }body is parsed as JSON when possible, kept as a string otherwise, so
${{ steps.notify-backend.output.body.some_field }} works against JSON APIs.
Non-2xx responses map into the failure taxonomy: 5xx and 429 are retryable
(rate_limited / retryable kinds) under a retry: block; 4xx are terminal. Timeouts
are rpc_timeout-class and retryable.
rflow_version: 1
name: trade-hooks
config:
port: 3940
db_connection: ${DATABASE_URL}
workflows:
poll-backend:
trigger:
cron: { expression: "*/5 * * * *" }
steps:
- id: notify-backend
http_call:
url: ${BACKEND_URL}/hooks/trade
retry:
max_attempts: 3
backoff: 10s