Security
To protect your data and ensure secure webhook delivery, the platform provides multiple layers of security. Following these measures and best practices helps prevent unauthorized access, payload tampering, and replay attacks.
1. Originating IP Address Verification
The platform delivers notifications through a set of predefined static IP addresses. Customers are encouraged to whitelist these IPs to ensure only requests from trusted sources are accepted:
- Static IPs:
34.138.140.223
34.138.161.100
35.231.250.193
35.196.71.29
34.138.56.192
Note: Although these IPs are specific to our webhook provider, they may still be shared with other customers of the provider. While whitelisting is a helpful security layer, it should not be the only method you rely on.
2. Payload HMAC Signature Verification
To verify that notifications genuinely originate from the platform, each payload is signed using a SHA-256 HMAC signature. This signature is included in the X-Synaps-Signature
header and can be verified using your unique signing secret, shared per customer.
Steps to Verify the Signature
- Retrieve the Signature: Obtain the value of the
X-Synaps-Signature
header from the incoming request. - Recreate the Signature: Use the same signing secret and SHA-256 HMAC algorithm to hash the payload data.
- Compare Signatures: Ensure that the recreated signature matches the signature in the header. A match confirms the authenticity of the payload.
Here are some code snippets to help you verify the signature in different programming languages:
- Go
- Python
- JavaScript
- TypeScript
type Server struct {
// Shared secret used to sign the payload
Secret string
}
// Retrieve the signature from the request and decode it
func retrieveSignature(h http.Header) ([]byte, error) {
base64Signature := h.Get("X-Synaps-Signature")
signature, err := base64.StdEncoding.DecodeString(base64Signature)
if err != nil {
return nil, fmt.Errorf("failed to decode signature: %w", err)
}
return signature, nil
}
func (s *Server) verify(b []byte, h http.Header) error {
signature, err := retrieveSignature(h)
if err != nil {
return err
}
// Compute the HMAC signature from the request body with the shared secret
hasher := hmac.New(sha256.New, []byte(s.Secret))
hasher.Write(b)
computed := hasher.Sum(nil)
// Compare the computed signature with the received signature
if !hmac.Equal(signature, computed) {
return errors.New("signature mismatch")
}
return nil
}
import hmac
import hashlib
import base64
# Retrieve the signature and decode it
def retrieve_signature(headers):
encoded_signature = headers.get("x-synaps-signature")
if encoded_signature is None:
return None
return base64.b64decode(encoded_signature)
def verify(secret, body, headers):
signature = retrieve_signature(headers)
if signature is None:
return False
# Compute the HMAC digest
digest = hmac.new(secret, body, hashlib.sha256).digest()
# Compare the computed digest with the signature
return hmac.compare_digest(signature, digest)
import { createHmac } from "crypto";
// Retrieve the signature from the headers and decode it from base64
const retrieveSignature = (headers) => {
const signature = headers["x-synaps-signature"];
if (!signature) {
throw new Error("Signature not found");
}
return Buffer.from(signature, "base64");
};
export const VerifySignature = (secret, body, headers) => {
const signature = retrieveSignature(headers);
// Compute the digest of the body using the secret
const digest = createHmac("sha256", secret).update(body).digest();
// Compare the computed digest with the signature
return Buffer.compare(signature, digest) === 0;
};
import { createHmac } from "crypto";
// Retrieve the signature from the headers and decode it from base64
const retrieveSignature = (headers: Record<string, string>): Buffer => {
const signature = headers["x-synaps-signature"];
if (!signature) {
throw new Error("Signature not found");
}
return Buffer.from(signature, "base64");
};
export const VerifySignature = (
secret: string,
body: Uint8Array,
headers: Record<string, string>
): boolean => {
const signature = retrieveSignature(headers);
// Compute the digest of the body using the secret
const digest = createHmac("sha256", secret).update(body).digest();
// Compare the computed digest with the signature
return Buffer.compare(signature, digest) === 0;
};
3. Preventing Replay Attacks
To guard against replay attacks, two safeguards are provided:
- Notification Expiration: Each webhook payload includes a
created_at
field. Customers should implement a threshold (e.g., 5-10 minutes) to reject any notifications received after this expiration window.
Tip: Ensure this threshold is compatible with the maximum retry timeout, so retries don’t unintentionally result in expired notifications.
- Idempotency Key: Every webhook payload includes a unique
idempotency_key
. You can store this key temporarily (e.g., in a Redis cache) to check if a notification has been processed previously, preventing duplicate processing.
Example: Store idempotency keys for 24 hours and discard any events with duplicate keys within that timeframe.
Combining Both Methods
An effective approach to preventing replay attacks is to use a mix of both expiration and idempotency key checks. For example, store each idempotency_key
with a 24-hour expiration in a temporary store like Redis, and discard any incoming notifications older than your chosen threshold.
Best Practices
To further enhance security and reliability, follow these best practices when implementing webhooks:
Whitelist IPs and Verify Signatures
- Whitelist IP Addresses: Accept incoming webhook requests only from the designated IPs to reduce the chance of unauthorized access.
- Always Verify Signatures: Signature verification ensures that payloads are authentic and untampered. Implement this verification for all incoming notifications.
Implement Flexible Payload Handling
- Handle Unexpected Fields: To accommodate future payload changes, configure your webhook handler to ignore unrecognized fields. This prevents errors if new fields are added to the payload.
- Reject Incomplete Payloads: Validate that the payload contains all expected fields to avoid incomplete or invalid data.
Error Handling and Retry Management
- Log Errors and Monitor Retries: Set up logging to track failed webhook deliveries and monitor retry attempts. This will help you troubleshoot delivery issues more effectively.
- Configure Proper HTTP Response Codes: Respond with appropriate status codes (
2xx
for success,5xx
for server errors) to let the platform know when to retry or stop delivery attempts.
Keep Payload Processing Efficient
- Optimize Endpoint Performance: Ensure your webhook handler can process events quickly to prevent timeouts.
- Avoid Blocking Operations: Offload heavy processing tasks to background jobs or asynchronous functions, so your webhook endpoint responds promptly.
Secure Your Endpoint
- Use HTTPS: Ensure that your webhook endpoint uses HTTPS to encrypt data in transit, protecting it from interception.
By following these security measures and best practices, you can ensure that your webhook implementation is secure, reliable, and ready to handle real-time notifications from the platform.