API Reference

Webhooks

Receive real-time notifications when invoice statuses change or withdrawals are processed. All webhooks are signed with HMAC-SHA256 for security.

Available Events

Invoice Events

invoice.created

New invoice created

invoice.detected

Payment detected on blockchain (0 confirmations)

invoice.partial

Partial payment received (less than expected amount)

invoice.overpaid

Overpayment received (more than expected amount)

invoice.confirmed

Payment confirmed with required block confirmations

invoice.processing_payout

Settlement in progress (sweep/split started)

invoice.paid_out

Funds distributed to fee/custody wallets and credited to balance

invoice.expired

Invoice expired without full payment

invoice.failed

Settlement failed

invoice.late_payment

Payment received after invoice expiration

Withdrawal Events

withdrawal.created

New withdrawal request created

withdrawal.completed

Withdrawal processed successfully

withdrawal.failed

Withdrawal failed

Order Events (Storefront)

order.created

New order placed

order.paid

Order payment confirmed

order.fulfilled

Order marked as fulfilled

order.cancelled

Order cancelled

Create Webhook Endpoint

POST/v1/webhooks/endpoints

Register a new webhook endpoint for your tenant

create-webhook.js
const response = await fetch('https://api.fromchain.plus/v1/webhooks/endpoints', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer gw_live_...',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    url: 'https://yourdomain.com/api/webhooks',
    enabled: true,
    eventsSubscribed: [
      'invoice.confirmed',
      'invoice.paid_out',
      'invoice.failed',
      'withdrawal.created',
      'withdrawal.completed',
      'withdrawal.failed'
    ]
  })
});

const endpoint = await response.json();
// {
//   "id": "wh_endpoint_abc123",
//   "url": "https://yourdomain.com/api/webhooks",
//   "secret": "whsec_xyz789...",  // Store this securely!
//   "enabled": true,
//   "eventsSubscribed": ["invoice.confirmed", "invoice.paid_out", "invoice.failed"],
//   "createdAt": "2025-12-18T10:00:00Z"
// }

Verifying Webhook Signatures

Every webhook includes HMAC-SHA256 signature headers for verification:

POST /api/webhooks HTTP/1.1
Host: yourdomain.com
Content-Type: application/json
X-Webhook-Id: evt_abc123
X-Webhook-Timestamp: 1702905600000
X-Webhook-Signature: v1=a1b2c3d4e5f6...
X-Tenant-Id: tenant_xyz789

{
  "id": "evt_abc123",
  "type": "invoice.partial",
  "createdAt": "2025-12-18T11:00:00.000Z",
  "data": {
    "invoiceId": "inv_123",
    "tenantId": "tenant_xyz",
    "chain": "BSC",
    "token": "USDT",
    "depositAddress": "0x1234...5678",
    "amountExpected": "100.00",
    "amountReceived": "50.00",
    "status": "PARTIALLY_PAID",
    "expiresAt": "2025-12-18T11:30:00.000Z",
    "confirmationsRequired": 5,
    "txHashes": ["0xabc...def"],
    "metadata": {}
  }
}

Verify the signature to ensure the webhook came from FromChain:

verify-webhook.js
import crypto from 'crypto';

function verifyWebhook(
  payload: string,        // Raw request body
  timestamp: string,      // X-Webhook-Timestamp header
  signature: string,      // X-Webhook-Signature header
  secret: string          // Your endpoint secret
): boolean {
  // Construct the signed payload
  const signedPayload = `${timestamp}.${payload}`;
  
  // Compute HMAC
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
  
  // Compare signatures (constant-time comparison)
  const receivedSignature = signature.replace('v1=', '');
  
  return crypto.timingSafeEqual(
    Buffer.from(expectedSignature),
    Buffer.from(receivedSignature)
  );
}

// Express.js example
app.post('/api/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'] as string;
  const timestamp = req.headers['x-webhook-timestamp'] as string;
  const payload = req.body.toString();
  
  // Verify timestamp to prevent replay attacks
  const now = Date.now();
  const webhookTime = parseInt(timestamp);
  if (Math.abs(now - webhookTime) > 5 * 60 * 1000) { // 5 minutes
    return res.status(400).send('Timestamp too old');
  }
  
  // Verify signature
  if (!verifyWebhook(payload, timestamp, signature, process.env.WEBHOOK_SECRET!)) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process the webhook
  const event = JSON.parse(payload);
  console.log('Valid webhook received:', event.type);
  
  res.status(200).send('OK');
});

Retry Behavior

If your endpoint fails (non-2xx response), FromChain will retry with exponential backoff:

AttemptDelayTotal Time
1Immediate0s
230 seconds30s
32 minutes2m 30s
45 minutes7m 30s
515 minutes22m 30s
61 hour1h 22m
73 hours4h 22m
86 hours10h 22m

⚠️ Important: After 8 failed attempts, the delivery moves to a dead-letter queue. You can manually replay failed deliveries via the API.

Webhook Management

GET/v1/webhooks/endpoints

List all webhook endpoints

PATCH/v1/webhooks/endpoints/:id

Update endpoint URL or subscribed events

POST/v1/webhooks/endpoints/:id/rotate-secret

Rotate the webhook secret

POST/v1/webhooks/endpoints/:id/test

Send a test webhook event

POST/v1/webhooks/replay/:eventId

Manually replay a specific webhook event

GET/v1/webhooks/deliveries

List webhook delivery attempts

Best Practices

✅ Always verify signatures

Never process webhooks without verifying the HMAC signature to prevent spoofing.

✅ Respond quickly

Return a 200 response immediately. Process the webhook asynchronously using a queue.

✅ Handle idempotency

Use the X-Webhook-Id header to deduplicate webhook deliveries.

✅ Use HTTPS endpoints

Webhook URLs must use HTTPS in production for security.