When you set a Signing Secret on a Send Webhook automation action, Eloope signs every outgoing payload so your endpoint can verify the request actually came from Eloope and was not modified in transit.
Why sign webhooks
Webhooks are public HTTP endpoints. Without a signature, anyone who guesses or scrapes your URL could send forged events to your system — triggering downstream automations, syncing fake data into your accounting tool, or causing reconciliation errors.
A signed webhook lets your endpoint cryptographically verify two things on every request:
- Authenticity — the payload was signed with a secret only Eloope and your endpoint know.
- Integrity — the payload was not modified after Eloope signed it.
Plus a third property as a side effect: the signature includes a timestamp, so old captured payloads cannot be replayed at you days or weeks later.
Set a signing secret
- Open the automation in the workflow builder.
- Click the Send Webhook action node.
- In the configuration panel, set the Signing Secret field. Use a long random string (32+ characters). Store the same secret in your endpoint's environment.
- Save and activate the workflow.
Note: Leave the field blank to send unsigned webhooks. We recommend always setting a secret in production.
Headers Eloope sends
Once a signing secret is set, Eloope adds these headers to every webhook request:
| Header | Description |
|---|---|
X-Eloope-Signature |
The signature in the form t=<unix-seconds>,v1=<hex-sha256-hmac> |
X-Eloope-Webhook-Id |
A unique UUID per delivery — useful for de-duplication if Eloope retries |
Content-Type |
application/json |
The signature scheme is compatible with Stripe's webhook signature format, so existing libraries and patterns can be reused.
How to verify a signature
- Read the
X-Eloope-Signatureheader and split out thet=andv1=parts. - Compute
HMAC_SHA256(secret, "<t>.<raw_request_body>")and hex-encode the result. - Compare your computed hash against the
v1value using a constant-time comparison function (to defeat timing attacks). - Reject the request if the timestamp is more than ~5 minutes old (replay protection).
Critical: Use the raw request body — exactly the bytes Eloope sent — not a re-serialized JSON object. Even single-character differences (whitespace, key order, escape characters) will produce a different HMAC and your verification will always fail.
Node.js example
import crypto from 'crypto';
function verifyEloopeSignature(rawBody, signatureHeader, secret) {
if (!signatureHeader) return false;
const parts = Object.fromEntries(
signatureHeader.split(',').map((kv) => kv.trim().split('='))
);
const ts = parseInt(parts.t, 10);
const sig = parts.v1;
// Replay protection — reject anything older than 5 minutes
if (!ts || Math.abs(Date.now() / 1000 - ts) > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${ts}.${rawBody}`)
.digest('hex');
if (expected.length !== sig.length) return false;
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}
Python example
import hmac
import hashlib
import time
def verify_eloope_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
if not signature_header:
return False
parts = dict(kv.strip().split('=', 1) for kv in signature_header.split(','))
ts = int(parts.get('t', '0'))
sig = parts.get('v1', '')
# Replay protection — reject anything older than 5 minutes
if not ts or abs(time.time() - ts) > 300:
return False
expected = hmac.new(
secret.encode(),
f"{ts}.{raw_body.decode()}".encode(),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, sig)
De-duplicating retries
If your endpoint returns a non-2xx status, Eloope retries the delivery. The X-Eloope-Webhook-Id header stays the same across retries of the same event, so you can:
- Record processed
X-Eloope-Webhook-Idvalues for a short window (e.g., 24 hours). - On every incoming request, check whether the ID has already been processed.
- If it has, return
200 OKimmediately without re-running side effects.
This makes your endpoint idempotent against retry storms.
Rotating the signing secret
Update the Signing Secret field on the webhook action in the automation builder. The next delivery will use the new secret.
To avoid dropped deliveries during rotation, configure your endpoint to accept either the old or the new secret for a short cutover window, then remove the old secret once you've confirmed the new one is flowing correctly.
What happens without a secret
Webhooks sent without a signing secret still work — they just don't include the signature headers. Your endpoint will receive the body normally, but cannot verify that the request came from Eloope.
For internal-only test endpoints, this may be fine. For anything in production or anything that triggers state changes, always set a signing secret.