Skip to main content
Dunning is webhook-driven. Revtain hands you a one-tap card-update URL the moment a recovery exhausts retries. Your existing email, SMS, or push infrastructure delivers it — from your domain, with your branding. Wire the integration once; every failure after that runs automatically.
Revtain does not send customer-facing emails or SMS. Customers trust messages from the merchant they signed up with. Deliverability and brand-trust both win when the message comes from your domain rather than a recovery vendor.

Using a billing platform? Zero dunning code.

Clients on Chargebee, Stripe Billing, Recurly, Braintree Subscriptions, Zuora, or Shopify Recharge ship dunning with no extra integration. The connector receives recovery outcomes from Revtain and dispatches branded emails through the platform’s existing sender configuration — already pointed at your domain. Configure the connector webhook URL once during onboarding. Everything else runs through your billing platform’s own dunning engine, using the templates, schedule, and sender address you’ve already set up there.

Direct-API clients: ship the handler once

Write a single webhook endpoint that listens for recovery.failed and branches on the recommendedAction field. Because you own the customer relationship, you already hold the customer’s email — match the event back to your invoice (via revtainTransactionId, or the idempotencyKey you supplied to /execute) and send the message from your own infrastructure. Wire it once; every future failure runs through it.
recovery.failed does not contain the customer’s email address or a card-update link — by design. You know your own customer, and for the current direct-gateway model the customer updates their card in your billing portal or checkout, not on a Revtain-hosted page. Branch on recommendedAction to decide what to do.
The handler is short. Below are two ready-to-paste examples — drop into your backend, change the lookup + send calls to your own, and you’re done.
import express from 'express';
import crypto from 'crypto';

const app = express();

// Required for HMAC signature verification — keep the raw body.
app.post('/webhooks/revtain', express.raw({ type: 'application/json' }), async (req, res) => {
  // 1. Verify the webhook came from Revtain (constant-time).
  const signature = req.header('X-Revtain-Signature') || '';
  const expected = crypto
    .createHmac('sha256', process.env.REVTAIN_WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');
  const ok = signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  if (!ok) return res.status(401).end();

  const event = JSON.parse(req.body.toString());

  // 2. Route on event type + recommended action.
  if (event.event === 'recovery.failed') {
    const invoice = await yourDb.findInvoiceByRevtainTxn(event.revtainTransactionId);
    if (event.recommendedAction === 'request_card_update') {
      await yourEmailProvider.send({
        from: 'billing@yourdomain.com',
        to: invoice.customerEmail,                         // you already have this
        template: 'payment-failed',
        vars: { updateUrl: yourBillingPortalUrl(invoice), amount: event.amount },
      });
    }
    // 'retry_later' → do nothing, Revtain may retry. 'manual_review' → flag the account.
  }

  res.json({ received: true });
});
That is the entire integration. The endpoint runs forever — every future recovery failure for every customer fires it.

What happens end to end

1

Recovery engine exhausts retries

The payment ran through the available retry paths on the gateway that issued the original token. None succeeded.
2

Your webhook fires

recovery.failed arrives with the decline code, recommendedAction, and revtainTransactionId. You match it back to your invoice and customer.
3

Your existing infrastructure delivers the message

When recommendedAction is request_card_update, your handler emails the customer from your domain with a link into your billing portal / checkout to re-enter their card.
4

Customer updates their payment method

They update their card where your other billing already lives. Pass the refreshed gateway token on your next /execute call for that customer.
5

Recovery completes

The new card on file, or a later scheduled retry, closes the loop. A success fires recovery.success so you can mark the invoice paid.
Revtain also offers a fully hosted card-update page, but it requires Revtain’s secure vault integration. It is not active in the current direct-gateway launch model — that is why the flow above routes card updates through your own portal. If your account is provisioned with vault integration, the card.updated webhook and POST /api/recovery/update-card/generate come into play; your onboarding contact will tell you if that applies to you.

Operator alerts (for your team)

If you want Revtain to notify your team — not your customer — when a recovery fails, add those channels during onboarding:
{
  "notificationChannels": ["WEBHOOK", "EMAIL", "SMS"]
}
These alerts go to you, not the customer. Informational only: a $25.00 payment failed, consider reviewing the account.

Why webhook-only

Customer-facing email from a third-party recovery vendor lands in promotions or spam — the customer never opted into hearing from us, only from you. Email from your domain lands in the inbox. The webhook handoff keeps your sender reputation intact, your brand on every message, and your team in control of timing, copy, and channel. Revtain runs the recovery engine. You run the customer relationship.