YOUR_RESOURCE_ID with the ID of one of your webhooks or of the temporary webhook the listener creates. The sandbox send in Development Workflow also needs the messages:send:all scope and your verified sender domain in place of yourdomain.com. The scripted trigger needs jq.
Overview
The CLI provides tools for:- Streaming webhook events to your terminal over a WebSocket connection
- Forwarding events, signed, to a local endpoint
- Triggering test events
- Debugging webhook signatures and payloads
webhooks command reference.
Local Webhook Listener
Basic Usage
ahasend webhooks listen prints each event as text, whatever --output says. It runs until you press Ctrl+C or the connection closes.
webhooks:write:all scope. When the connection starts, the command prints a banner with the webhook ID, the forwarding secret and, with --forward-to, the forwarding URL.
--events filters in the CLI: events of other types are neither shown nor forwarded. It takes event keys or types, such as opened or message.opened.
Temporary Webhooks
Without--webhook-id, the API creates a temporary webhook named DevMode webhook for <API key label>. webhooks list does not show it. It stays after the command ends, until you delete it or the API does, as Events Without a Listener describes.
As soon as the API creates it, the command prints a note on stderr with its ID and the command that deletes it:
Existing Webhooks
With--webhook-id, the command listens through a webhook you created. A disabled webhook gets no events, so enable it first:
webhooks trigger included. Listening also turns on streaming for the webhook, and streaming stays on after the command ends, as Events Without a Listener describes.
Events Without a Listener
A webhook with streaming on keeps it when no listener is connected: the API keeps its events for the next listener, up to the last 100. When 5 minutes pass with no new event, the API drops the kept events. The nextwebhooks listen --webhook-id of the same webhook prints the kept events first, each marked REPLAY:.
When an event arrives while 100 events are already kept, the API drops the kept events and turns off streaming for the webhook:
- A temporary webhook is deleted.
- A webhook you created keeps sending events to its URL. The next
webhooks listen --webhook-idturns streaming on again.
Available Events
Thewebhooks event types lists every event you can listen for and trigger. See the event reference for each payload.
Forwarding Events
Forward to a Local Endpoint
--forward-to sends each event to your handler as an HTTP POST with a JSON body. The URL must be HTTP or HTTPS with a host name and no user name or password; another URL fails with exit code 4. A plain HTTP URL for a host other than localhost or a loopback address prints a warning on stderr.
--skip-verify applies only to the --forward-to endpoint, and needs it: without --forward-to, the command fails with exit code 4. The connection to AhaSend is always verified.
Forwarding Secret and Signatures
When the connection starts, the banner printsForwarding secret (signs forwarded events): <secret>. The command makes a new secret on each run, also with --webhook-id: it is not the secret of that webhook.
Each forwarded request carries the Standard Webhooks headers:
webhook-id- Unique message identifierwebhook-timestamp- Unix timestampwebhook-signature- HMAC-SHA256 signature for verification
Order and Delivery
The command forwards events one at a time, in the order they arrive, with their body byte for byte as the stream sent it, so the signature covers exactly what the API sent. A slow endpoint does not hold up the stream: up to 100 events, and up to 32 MiB of their data, wait for it, in order. When an event would go over either limit, the command does not forward it and says so on stderr. A forwarded request is not sent again. When the request fails, the command logs an error on stderr; when it gets a response outside the 2xx range, the command logs a warning. When the stream ends, the command waits for the events not yet forwarded. Press Ctrl+C to skip the wait.Triggering Test Events
Development Testing
webhooks trigger asks the API to send test events to a webhook. Use the webhook ID that the listener’s banner shows to see the events in your terminal.
Event Simulation Scenarios
Integration Testing
Development Workflow
Scripted Trigger
webhooks trigger exits nonzero when the API refuses the request, so a script with set -e stops on an API error:
Connection and Exit Codes
The listener does not reconnect. It exits with:0when the server closes the connection normally, or when you press Ctrl+C7when the connection drops, or the server closes it with an error4when you decline to take over the stream from another connection
Debugging
Output Options
Session established line and the events, with separator lines between them. Notes, warnings, and the logs of --verbose and --debug go to stderr.
Common Issues
Signature Verification Failed: Use the forwarding secret of the current run, as the banner prints it, as the raw UTF-8 HMAC key. Do not base64-decode it, and do not use the webhook’s own secret. Each run prints a new secret. Events Not Received: Check that the webhook is enabled, that--events does not filter the events out, and that your local server is running. Run with --debug to see each forward and its response status.
Connection Lost: The listener does not reconnect. Start it again with --webhook-id and the same webhook ID to receive the kept events.
Best Practices
- Start Simple: Test with single events before complex flows
- Use Sandbox Mode: Test with sandbox emails to avoid sending real messages
- Verify Signatures: Always validate webhook signatures in production
- Clean Up: Delete temporary webhooks when you finish testing
- Test Error Cases: Simulate failures to ensure proper error handling

