Txn.pro - API Documentation
Sandbox LoginChangelogStatus Page
  • Getting setup
    • Environments
    • Creating a Sandbox account
    • Back office user roles
    • Creating your API keys
    • Postman collection
  • API Basics
    • API specification
    • API status
    • Supported currencies
    • Date and time format
    • Authenticating the API
    • Webhooks
      • Verifying webhooks
      • Source IP addresses
      • Retrying schedule
    • Rate limits
  • Hosted pages
  • Accounts
    • Accounts concept
    • Listing accounts
    • Reading account
    • Listing account addresses
    • Creating account address
    • Listing account transactions
    • Reading account transaction
  • Exchange
    • Exchange concept
    • Supported currency pairs
    • Reading exchange rates
    • Exchange webhooks
    • Creating quote
    • Confirming quote
  • Invoices
    • Invoices concept
    • Invoice status flow
    • Invoices in cashier
    • Invoice limitations
    • Listing invoice currency pairs
    • Invoice webhooks
    • Creating invoice
    • Reading invoice
    • Listing invoices
    • Invoice refunds
  • Payouts
    • Payouts concept
    • Payout status flow
    • Payout limitations
    • Listing payout currency pairs
    • Validating addresses
    • Payout webhooks
    • Creating payout
    • Reading payout
    • Listing payouts
  • Mass Payouts
  • PAYMENT LINKS
    • Payment links concept
    • Creating payment link
    • Expiring payment link
    • Reading payment link
    • Listing payment links
  • CHANNELS
    • Channels concept
    • Channels deposit flow
    • Listing channel currency pairs
    • Channel webhooks
    • Creating channel
    • Reading channel
    • Updating channel
    • Listing channels
    • Listing channel deposits
    • Reading channel deposit
  • (BETA) Fiat Settlements
    • Important information
    • Bank withdrawal webhooks
    • Bank deposit webhooks
    • Listing bank accounts
    • Reading bank account
    • Previewing bank withdrawal
    • Creating bank withdrawal
    • Listing bank withdrawals
    • Reading bank withdrawal
    • Listing bank deposits
    • Reading bank deposit
Powered by GitBook
On this page
  • Constructing the Signed Content
  • Determining the Expected Signature
  • Verify Timestamp
  1. API Basics
  2. Webhooks

Verifying webhooks

Each webhook call includes three headers with additional information used for verification:

  • svix-id: The unique message identifier for the webhook message. This identifier is unique across all messages but will remain the same when the same webhook is being resent (e.g., due to a previous failure).

  • svix-timestamp: The timestamp in seconds since the epoch.

  • svix-signature: The Base64-encoded list of signatures, space-delimited.

Constructing the Signed Content

The content to sign is created by concatenating the ID, timestamp, and payload, separated by a period (.). In code, it would look something like this:

const signedContent = `${svix_id}.${svix_timestamp}.${body}`;

Where body is the raw body of the request. The signature is highly sensitive to any changes, so even a minor modification in the body will result in a completely different signature. Therefore, you should not alter the body in any way before verifying it.

Determining the Expected Signature

Txn uses HMAC with SHA-256 to sign its webhooks.

To calculate the expected signature, you should HMAC the signedContent (constructed as described above) using the Base64-decoded portion of your signing secret (the part after the whsec_ prefix) as the key. For example, if your secret is whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw, you should use MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw as the key.

Here's an example of hot to calcialte the signature in Ruby on Rails:

Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha256"), Base64.decode64(secret), "#{msgId}.#{timestamp}.#{payload}")).strip

Here’s an example of how to calculate the signature in Node.js:

const crypto = require('crypto');

const signedContent = `${svix_id}.${svix_timestamp}.${body}`;
const secret = "whsec_5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH";

// Base64 decode the secret key
const secretBytes = Buffer.from(secret.split('_')[1], "base64");
const signature = crypto
  .createHmac('sha256', secretBytes)
  .update(signedContent)
  .digest('base64');

console.log(signature);

The generated signature should match one of the signatures sent in the svix-signature header.

The svix-signature header contains a list of space-delimited signatures with their corresponding version identifiers. The list usually contains one signature, but there can be multiple. For example:

v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=
v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo=
v2,MzJsNDk4MzI0K2VvdSMjMTEjQEBAQDEyMzMzMzEyMwo=

Before verifying the signature, make sure to remove the version prefix and delimiter (e.g., v1,).

Security Note: Use a constant-time string comparison method to compare signatures and prevent timing attacks.

Verify Timestamp

As mentioned above, Txn includes the timestamp of the attempt in the svix-timestamp header. Compare this timestamp against your system's timestamp to ensure it falls within your acceptable tolerance range, helping to prevent timestamp attacks.

PreviousWebhooksNextSource IP addresses

Last updated 8 months ago