HMAC Signature Verification
Smart Webhooks support HMAC-SHA256 signature verification to ensure incoming requests are authentic. When a signing_secret is configured on a webhook, the execution endpoint verifies the request signature before processing.
How It Works
- The webhook source (GitHub, Stripe, etc.) signs the request body using a shared secret
- The signature is sent in a request header
- Aerostack verifies the signature against the shared secret
- If the signature matches, the webhook is processed. Otherwise, it is rejected.
Setting a Signing Secret
Set the signing_secret when creating or updating a Smart Webhook:
# On creation
curl -X POST https://api.aerostack.dev/api/smart-webhooks \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "github-webhook",
"workspace_id": "ws_abc123",
"instructions": "Process GitHub events...",
"source_type": "github",
"signing_secret": "your_shared_secret_here"
}'# Update an existing webhook
curl -X PATCH https://api.aerostack.dev/api/smart-webhooks/swh_your_id \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"signing_secret": "your_new_secret_here"
}'The signing secret can be up to 256 characters long.
Supported Signature Headers
The execution endpoint checks for the signature in these headers (in order of priority):
| Header | Used By |
|---|---|
X-Hub-Signature-256 | GitHub |
X-Signature-256 | Generic |
X-Webhook-Signature | Generic / custom services |
The first header found is used for verification. If none of these headers are present and a signing secret is configured, the request is silently rejected.
Signature Format
The expected signature format is:
sha256=<hex_digest>Where <hex_digest> is the HMAC-SHA256 hash of the raw request body using the signing secret as the key, encoded as lowercase hexadecimal.
If the signature header value does not start with sha256=, the prefix is automatically added before comparison.
Provider-Specific Setup
GitHub
- In your GitHub repo, go to Settings > Webhooks > Edit (or Add webhook)
- Set the Secret field to the same value as your Smart Webhook’s
signing_secret - GitHub will automatically send the
X-Hub-Signature-256header with each delivery
# Create webhook with GitHub secret
curl -X POST https://api.aerostack.dev/api/smart-webhooks \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "github-events",
"workspace_id": "ws_abc123",
"instructions": "Handle GitHub events...",
"source_type": "github",
"signing_secret": "whsec_your_github_secret"
}'Stripe
Stripe uses a different signature format (Stripe-Signature header with t= timestamp and v1= signature). Currently, Smart Webhooks check for X-Hub-Signature-256, X-Signature-256, or X-Webhook-Signature headers only.
Stripe’s native Stripe-Signature header is not currently supported for automatic verification. To use Stripe webhooks with HMAC verification, you can set up a proxy that converts the Stripe signature to the X-Webhook-Signature format, or omit the signing_secret and rely on Stripe’s IP allowlisting instead.
Custom Services
For your own services, compute the HMAC-SHA256 signature and send it in any of the supported headers:
# Compute signature and send webhook
SECRET="your_shared_secret"
BODY='{"event":"user.created","data":{"id":"usr_123"}}'
SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)
curl -X POST https://api.aerostack.dev/api/webhooks/smart/my-webhook \
-H "Content-Type: application/json" \
-H "X-Webhook-Signature: sha256=$SIGNATURE" \
-d "$BODY"Security Notes
No Signing Secret
If signing_secret is not set on a webhook, all incoming requests are accepted without signature verification.
Running without a signing secret is not recommended for production use. Without signature verification, anyone who discovers your webhook URL can send arbitrary payloads that will be processed by the AI agent and trigger tool calls in your workspace.
Verifying Your Setup
Since all webhook responses return { ok: true } regardless of outcome, use these methods to verify your setup:
-
Check run history — After sending a test webhook, check if a run was recorded:
curl https://api.aerostack.dev/api/smart-webhooks/swh_your_id/runs \ -H "Authorization: Bearer YOUR_JWT_TOKEN"If the run appears, the signature was valid.
-
GitHub delivery log — GitHub shows delivery status in the webhook settings page. A
200response means Aerostack received it, but check the runs endpoint to confirm it was actually processed. -
Send a test with a known-good signature — Use the curl example from the “Custom Services” section above with your actual signing secret.