Skip to content

Webhooks

Receive real-time notifications for Rain events and verify their authenticity using HMAC-SHA256 signatures.

Webhook Event History

Query past webhook events delivered to your endpoint:

typescript
// Get all webhook events
const { data: events } = await rain.webhooks.getWebhooks();

// Filter by resource, action, or time range
const { data: events } = await rain.webhooks.getWebhooks({
  resourceType: "transaction",
  resourceAction: "created",
  requestSentAtAfter: "2026-01-01T00:00:00Z",
  limit: 50
});

// Get a specific webhook event
const { data: event } = await rain.webhooks.getWebhook("webhook-event-id");

WebhookEvent Structure

typescript
interface WebhookEvent {
  id: string;
  requestBody: RainWebhook; // The webhook payload
  requestSentAt: string; // When Rain sent the webhook
  responseReceivedAt?: string; // When your server responded
  responseStatus?: number; // Your server's HTTP status
  responseBody?: unknown; // Your server's response
  attemptCount?: number; // Delivery attempt number
}

Verify Webhook Signatures

Rain signs every webhook payload with HMAC-SHA256 using your API key. Always verify signatures before processing.

typescript
const isValid = rain.webhooks.verifyWebhookSignature(
  requestBody, // Raw request body (string or object)
  signature // Value from the "signature" header
);

The SDK uses timing-safe comparison to prevent timing attacks.

Parse Webhook Payloads

typescript
const webhook = rain.webhooks.parseWebhook(requestBody);

console.log(webhook.id); // Webhook event ID
console.log(webhook.resource); // "transaction", "card", "collateral", etc.
console.log(webhook.action); // "created", "updated", "completed", etc.
console.log(webhook.body); // Event-specific payload

Webhook Resources and Actions

ResourceActionsDescription
transactionrequested, created, updated, completedSpend, collateral, payment, and fee transactions
cardcreated, updatedCard status changes
collateralcreated, updated, completedCollateral deposits and withdrawals
companycreated, updatedCompany status changes
usercreated, updatedUser status changes
contractcreated, updatedSmart contract events
reportcreated, completedReport generation events
disputescreated, updatedDispute lifecycle events

Express.js Handler Example

typescript
import express from "express";
import { createClient } from "rain-sdk";

const app = express();
const rain = createClient({ baseURL, apiKey });

app.post("/webhooks/rain", express.json(), (req, res) => {
  const signature = req.headers["signature"] as string;

  if (!signature) {
    return res.status(400).json({ error: "Missing signature header" });
  }

  const isValid = rain.webhooks.verifyWebhookSignature(req.body, signature);
  if (!isValid) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const webhook = rain.webhooks.parseWebhook(req.body);

  switch (`${webhook.resource}.${webhook.action}`) {
    case "transaction.created":
      handleNewTransaction(webhook.body);
      break;
    case "card.updated":
      handleCardUpdate(webhook.body);
      break;
    case "collateral.completed":
      handleCollateralDeposit(webhook.body);
      break;
  }

  res.json({ received: true });
});

Signing Key Rotation

Rotate webhook signing keys without downtime using the two-step process:

Step 1: Create a Secondary Key

typescript
const { data: keyResponse } = await rain.webhooks.createSecondarySigningKey();

console.log(keyResponse.currentSigningApiKey.name); // Current primary
console.log(keyResponse.newSecondarySigningApiKey.key); // New secondary key

Update your webhook handler to accept signatures from either key during the transition.

Step 2: Promote Secondary to Primary

Once you have verified the secondary key works:

typescript
const { data: promoted } = await rain.webhooks.promoteSecondaryKeyToPrimary();

console.log(promoted.oldPrimarySigningApiKey.name); // Deleted
console.log(promoted.newPrimarySigningApiKey.key); // Now primary

DANGER

Promoting the secondary key deletes the old primary key. Verify your handler works with the secondary key before promoting.

Signature Key Scopes

Signature keys can be managed at three scopes for payment and withdrawal operations:

typescript
// Company scope
await rain.signatures.getCompanyPaymentSignature("company-id", params);
await rain.signatures.getCompanyWithdrawalSignature("company-id", params);

// Tenant scope
await rain.signatures.getTenantPaymentSignature("tenant-id", params);
await rain.signatures.getTenantWithdrawalSignature("tenant-id", params);

// User scope
await rain.signatures.getUserPaymentSignature("user-id", params);
await rain.signatures.getUserWithdrawalSignature("user-id", params);

SignatureParams

typescript
interface SignatureParams {
  token: string; // Token contract address
  amount: string; // Amount in token units
  adminAddress: string; // Admin wallet address
  chainId?: number; // Target chain ID
  isAmountNative?: boolean;
  rainCollateralContractId?: string;
  recipientAddress?: string; // Required for withdrawals
}

Signatures are returned as either ready (with data and salt) or pending (with optional retryAfter).

Webhooks has loaded