Skip to main content

What are Webhooks?

Webhooks are HTTP callbacks that Revolv3 sends to your server when events happen. Instead of you constantly checking (“polling”) the API to see if something changed, Revolv3 automatically notifies you when events occur. Why use webhooks:
  • Real-time updates: Know immediately when payments succeed or fail
  • Efficient: No need to constantly poll the API
  • Better UX: Update your UI instantly when events happen
  • Essential for ACH: ACH payments take days—webhooks tell you when they complete

How Webhooks Work

The Flow

  1. Event happens in Revolv3 (payment succeeds, invoice status changes, etc.)
  2. Revolv3 sends HTTP POST to your webhook URL
  3. Your server receives the webhook and processes it
  4. Your server responds with 200 OK to confirm receipt
  5. You update your system based on the event data

Webhook Format

Revolv3 sends webhooks with this structure:
{
  "Body": "{\"Invoice\":{\"InvoiceId\":330973,\"InvoiceStatus\":\"Paid\"...},\"EventDateTime\":\"2025-01-27T18:18:48Z\",\"EventType\":\"InvoiceStatusChanged\",\"RevolvMerchantId\":579}",
  "Entropy": "b39d4914-cb32-46d0-b3ab-e832539d3d7b"
}
Understanding the fields:
  • Body: A JSON string containing the event data (you need to parse it)
  • Entropy: A random string for additional security
The Body field is a string, so you need to parse it as JSON to access the data inside.

Available Webhook Events

Revolv3 sends webhooks for these events:
Event TypeWhen It’s SentWhat It Means
SubscriptionCreatedSubscription is successfully createdNew subscription is active
SubscriptionChangedSubscription is updatedSubscription details changed
SubscriptionFailedSubscription creation failedCouldn’t create the subscription
InvoiceCreatedNew invoice is createdInvoice ready for payment
InvoiceStatusChangedInvoice status changesPayment succeeded, failed, or status updated
InvoiceAttemptCreatedPayment attempt is madeRevolv3 tried to process a payment
InvoiceAttemptStatusChangedPayment attempt status changesAttempt succeeded or failed
WebhookTestDuring webhook setupTest event to verify your endpoint works
Note: The ACHInvoiceStatusChanged webhook is deprecated. Use InvoiceStatusChanged instead—it covers all payment types including ACH.

Understanding Event Data

Parsing the Body

The Body field is a JSON string, so you need to parse it: Before parsing (what you receive):
{
  "Body": "{\"Invoice\":{\"InvoiceId\":330973...},\"EventType\":\"InvoiceStatusChanged\"}",
  "Entropy": "b39d4914-cb32-46d0-b3ab-e832539d3d7b"
}
After parsing (what you use):
{
  "Invoice": {
    "InvoiceId": 330973,
    "InvoiceStatus": "Paid",
    "Subtotal": 0.13,
    "Total": 0.13
  },
  "EventDateTime": "2025-01-27T18:18:48.3105823Z",
  "EventType": "InvoiceStatusChanged",
  "RevolvMerchantId": 579
}

Event Data Structure

Each event includes:
  • Event-specific data: The main object (Invoice, Subscription, Attempt, etc.)
  • EventDateTime: When the event occurred (ISO 8601 timestamp)
  • EventType: The type of event (see table above)
  • RevolvMerchantId: Your merchant ID (useful for multi-merchant setups)

Webhook Examples

Subscription Created

When: A subscription is successfully created Body (after parsing):
{
  "Subscription": {
    "SubscriptionId": 2691,
    "CustomerId": 9687,
    "MerchantSubscriptionRefId": "1234-5678-9101",
    "SubscriptionStatusType": "Current",
    "NextBillDate": "1/28/2025",
    "BillingPlans": [...]
  },
  "EventDateTime": "2025-01-28T18:09:27.6118027Z",
  "EventType": "SubscriptionCreated",
  "RevolvMerchantId": 579
}
What to do: Store the subscription ID, update your system, notify the customer.

Invoice Status Changed

When: An invoice status changes (e.g., from Pending to Paid) Body (after parsing):
{
  "Invoice": {
    "InvoiceId": 330973,
    "InvoiceStatus": "Paid",
    "Subtotal": 0.13,
    "Total": 0.13,
    "NetworkTransactionId": "480213756277396"
  },
  "EventDateTime": "2025-01-27T18:18:48.3105823Z",
  "EventType": "InvoiceStatusChanged",
  "RevolvMerchantId": 579
}
What to do:
  • If status is “Paid”: Fulfill the order, send confirmation
  • If status is “Noncollectable”: Notify customer, ask for different payment method

Invoice Attempt Status Changed

When: A payment attempt’s status changes Body (after parsing):
{
  "Attempt": {
    "InvoiceId": 331122,
    "InvoiceAttemptStatus": "Fail",
    "Amount": 10.99,
    "ResponseMessage": "Decline"
  },
  "EventDateTime": "2025-01-28T18:15:49.7836702Z",
  "EventType": "InvoiceAttemptStatusChanged",
  "RevolvMerchantId": 579
}
What to do: Handle the payment failure, notify customer, potentially retry.

Webhook Security: Signature Verification

Revolv3 includes a signature in the x-revolv3-signature header so you can verify the webhook came from Revolv3.

Why Verify Signatures?

Security: Verifying signatures ensures:
  • The webhook actually came from Revolv3 (not a malicious third party)
  • The data wasn’t tampered with in transit
  • You’re processing legitimate events
Always verify signatures before processing webhook data.

How Signature Verification Works

Revolv3 uses HMAC-SHA256 to create a signature:
  1. Concatenate your webhook URL + $ + request body
  2. Create HMAC-SHA256 hash using your webhook key
  3. Encode the hash as Base64
  4. Compare with the x-revolv3-signature header

Verification Examples

C# Example

public static bool IsValidWebhookEventSignature(
    string requestBody,
    string signatureHeader,
    string signatureKey,
    string url)
{
    if (string.IsNullOrEmpty(signatureKey))
        throw new ArgumentNullException(nameof(signatureKey));
    if (string.IsNullOrEmpty(url))
        throw new ArgumentNullException(nameof(url));
    
    // Concatenate url + "$" + requestBody
    byte[] bytes = Encoding.UTF8.GetBytes(url + "$" + requestBody);
    
    // Create HMAC-SHA256 hash
    using var hmacsha256 = new HMACSHA256(Encoding.UTF8.GetBytes(signatureKey));
    
    // Compare signatures
    return Convert.ToBase64String(hmacsha256.ComputeHash(bytes)).Equals(signatureHeader);
}

PHP Example

function IsValidWebhookEventSignature(
    string $requestBody,
    string $signatureHeader,
    string $signatureKey,
    string $url
): bool {
    if (empty($signatureKey)) {
        throw new ArgumentNullException('signatureKey');
    }
    if (empty($url)) {
        throw new ArgumentNullException('url');
    }

    // Concatenate url + "$" + requestBody
    $data = $url . '$' . $requestBody;

    // Create HMAC-SHA256 hash
    $hmac = hash_hmac('sha256', $data, $signatureKey, true);

    // Compare signatures
    return base64_encode($hmac) === $signatureHeader;
}

JavaScript/Node.js Example

const crypto = require('crypto');

function isValidWebhookSignature(requestBody, signatureHeader, signatureKey, url) {
  if (!signatureKey || !url) {
    throw new Error('signatureKey and url are required');
  }
  
  // Concatenate url + "$" + requestBody
  const data = url + '$' + requestBody;
  
  // Create HMAC-SHA256 hash
  const hmac = crypto.createHmac('sha256', signatureKey);
  hmac.update(data);
  const hash = hmac.digest('base64');
  
  // Compare signatures (use constant-time comparison for security)
  return crypto.timingSafeEqual(
    Buffer.from(hash),
    Buffer.from(signatureHeader)
  );
}

Best Practices for Webhook Handling

1. Always Verify Signatures

Never process webhooks without verifying the signature first. This is a critical security requirement.

2. Respond Quickly

  • Respond with 200 OK as soon as you receive the webhook
  • Process asynchronously if processing takes time
  • Don’t block on long operations

3. Handle Duplicates

Webhooks may be sent multiple times. Make your handler idempotent:
  • Check if you’ve already processed this event
  • Use event IDs or timestamps to detect duplicates
  • Don’t process the same event twice

4. Log Everything

  • Log all incoming webhooks (for debugging)
  • Store event data for auditing
  • Track which events you’ve processed

5. Handle Errors Gracefully

  • If processing fails, log the error
  • Consider retrying failed processing
  • Don’t crash your server on webhook errors

Common Webhook Scenarios

Scenario 1: Payment Succeeds

Event: InvoiceStatusChanged Status: Paid What to do:
  1. Verify signature
  2. Parse the Body JSON
  3. Extract invoice ID and status
  4. Update order status to “paid”
  5. Fulfill the order (ship product, activate service, etc.)
  6. Send confirmation email to customer
  7. Respond with 200 OK

Scenario 2: ACH Payment Completes

Event: InvoiceStatusChanged Status: Paid (after 1-5 business days) What to do:
  1. Verify signature
  2. Parse the Body JSON
  3. Check if status is “Paid” or “Noncollectable”
  4. If Paid: Fulfill order, send confirmation
  5. If Noncollectable: Notify customer, ask for different payment method
  6. Respond with 200 OK

Scenario 3: Subscription Payment Fails

Event: InvoiceAttemptStatusChanged Status: Fail What to do:
  1. Verify signature
  2. Parse the Body JSON
  3. Check the failure reason
  4. Notify customer that payment failed
  5. Pause subscription (optional)
  6. Give customer a grace period to update payment method
  7. Respond with 200 OK

Troubleshooting

Webhook Not Received

Check:
  • Is your endpoint publicly accessible? (webhooks can’t reach localhost)
  • Is your endpoint returning 200 OK?
  • Are you checking the correct URL?
  • Is your server firewall blocking requests?

Signature Verification Fails

Check:
  • Are you using the correct webhook key?
  • Are you concatenating URL + ”$” + body correctly?
  • Are you using HMAC-SHA256 and Base64 encoding?
  • Is the URL exactly as configured (including https://)?

Duplicate Events

Solution:
  • Implement idempotency checking
  • Store event IDs or timestamps
  • Skip processing if already handled

Next Steps