Skip to main content

Webhook Implementation

Receive real-time notifications when events occur in usmewe.

Overview

Webhooks allow your application to react to events as they happen:
  • Loan funded/repaid
  • Vault deposits/withdrawals
  • Trust Score changes
  • Social Vault alerts

Setting Up Webhooks

1. Create an Endpoint

Create an HTTPS endpoint to receive webhook events:
// Express.js example
import express from 'express';

const app = express();

app.post('/webhooks/usmewe', express.json(), (req, res) => {
  const event = req.body;

  console.log('Received event:', event.type);

  // Process the event
  handleEvent(event);

  // Respond quickly
  res.status(200).send('OK');
});

app.listen(3000);

2. Register the Webhook

Register your endpoint via API:
curl -X POST "https://api.usmewe.com/v1/webhooks" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/usmewe",
    "events": ["loan.funded", "loan.repaid", "trust_score.updated"],
    "secret": "your_webhook_secret_here"
  }'

3. Verify Signatures

Always verify webhook signatures:
import crypto from 'crypto';

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  const signatureBuffer = Buffer.from(signature.replace('sha256=', ''));
  const expectedBuffer = Buffer.from(expectedSignature);

  return crypto.timingSafeEqual(signatureBuffer, expectedBuffer);
}

// In your webhook handler
app.post('/webhooks/usmewe', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-usmewe-signature'] as string;
  const payload = req.body.toString();

  if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
    console.error('Invalid webhook signature');
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(payload);
  handleEvent(event);

  res.status(200).send('OK');
});

Event Structure

All webhook events follow this structure:
{
  "id": "evt_abc123xyz",
  "type": "loan.funded",
  "timestamp": "2024-01-15T12:00:00Z",
  "data": {
    // Event-specific data
  }
}

Available Events

Loan Events

loan.created

{
  "type": "loan.created",
  "data": {
    "loanId": "loan_xyz789",
    "userId": "user_abc123",
    "amount": "50.00",
    "duration": 14,
    "status": "pending"
  }
}

loan.funded

{
  "type": "loan.funded",
  "data": {
    "loanId": "loan_xyz789",
    "userId": "user_abc123",
    "amount": "50.00",
    "netAmount": "45.00",
    "txHash": "0x...",
    "dueDate": "2024-01-29T12:00:00Z"
  }
}

loan.repaid

{
  "type": "loan.repaid",
  "data": {
    "loanId": "loan_xyz789",
    "userId": "user_abc123",
    "amountPaid": "51.35",
    "txHash": "0x...",
    "trustScoreChange": 2
  }
}

loan.overdue

{
  "type": "loan.overdue",
  "data": {
    "loanId": "loan_xyz789",
    "userId": "user_abc123",
    "daysOverdue": 1,
    "amountDue": "51.35"
  }
}

Vault Events

vault.deposit

{
  "type": "vault.deposit",
  "data": {
    "userId": "user_abc123",
    "usdcAmount": "1000.00",
    "tmUSDCReceived": "975.61",
    "txHash": "0x..."
  }
}

vault.withdraw

{
  "type": "vault.withdraw",
  "data": {
    "userId": "user_abc123",
    "tmUSDCRedeemed": "500.00",
    "usdcReceived": "512.50",
    "txHash": "0x..."
  }
}

Trust Score Events

trust_score.updated

{
  "type": "trust_score.updated",
  "data": {
    "userId": "user_abc123",
    "previousScore": 54,
    "newScore": 56,
    "change": 2,
    "reason": "on_time_repayment"
  }
}

Social Vault Events

social_vault.withdrawal_requested

{
  "type": "social_vault.withdrawal_requested",
  "data": {
    "vaultOwner": "user_abc123",
    "withdrawalId": "wd_abc123",
    "amount": "500.00",
    "recipient": "0x...",
    "requiredApprovals": 2
  }
}

social_vault.locked

{
  "type": "social_vault.locked",
  "data": {
    "vaultOwner": "user_abc123",
    "lockedAt": "2024-01-15T12:00:00Z",
    "reason": "duress_pin"
  }
}

Event Handling

async function handleEvent(event: WebhookEvent) {
  // Idempotency check
  if (await isEventProcessed(event.id)) {
    console.log('Event already processed:', event.id);
    return;
  }

  switch (event.type) {
    case 'loan.funded':
      await handleLoanFunded(event.data);
      break;

    case 'loan.repaid':
      await handleLoanRepaid(event.data);
      break;

    case 'trust_score.updated':
      await handleTrustScoreUpdated(event.data);
      break;

    case 'social_vault.locked':
      await handleVaultLocked(event.data);
      break;

    default:
      console.log('Unhandled event type:', event.type);
  }

  // Mark as processed
  await markEventProcessed(event.id);
}

async function handleLoanFunded(data: LoanFundedData) {
  // Update your database
  await db.loans.update({
    where: { externalId: data.loanId },
    data: {
      status: 'funded',
      fundedAt: new Date(),
      txHash: data.txHash
    }
  });

  // Notify the user
  await sendNotification(data.userId, {
    title: 'Loan Funded!',
    body: `Your loan of ${data.netAmount} USDC has been funded.`
  });
}

Error Handling

Retry Logic

We retry failed webhooks with exponential backoff:
AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
52 hours
624 hours

Handling Failures

app.post('/webhooks/usmewe', async (req, res) => {
  try {
    // Verify signature
    if (!verifySignature(req)) {
      return res.status(401).send('Invalid signature');
    }

    // Process event
    await handleEvent(req.body);

    res.status(200).send('OK');
  } catch (error) {
    console.error('Webhook processing error:', error);

    // Return 500 to trigger retry
    res.status(500).send('Processing failed');
  }
});

Testing Webhooks

Send Test Event

curl -X POST "https://api.usmewe.com/v1/webhooks/{webhookId}/test" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{"event": "loan.funded"}'

Local Development

Use ngrok or similar for local testing:
# Start ngrok
ngrok http 3000

# Update webhook URL
curl -X PATCH "https://api.usmewe.com/v1/webhooks/{webhookId}" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{"url": "https://abc123.ngrok.io/webhooks/usmewe"}'

Best Practices

Return 200 OK within 5 seconds. Process asynchronously:
app.post('/webhooks', (req, res) => {
  // Queue for async processing
  queue.add('process-webhook', req.body);

  // Respond immediately
  res.status(200).send('OK');
});
Use event IDs for idempotency:
const processed = await redis.get(`webhook:${event.id}`);
if (processed) return;

await handleEvent(event);
await redis.set(`webhook:${event.id}`, '1', 'EX', 86400);
Log all webhooks for debugging:
console.log('Webhook received:', {
  id: event.id,
  type: event.type,
  timestamp: new Date().toISOString()
});
Track webhook success rates and latencies.

Troubleshooting

Common Issues

IssueSolution
Invalid signatureCheck secret matches, verify raw body
Missing eventsConfirm webhook is active and events subscribed
TimeoutsProcess async, return 200 quickly
Duplicate processingImplement idempotency with event IDs

Check Delivery History

curl -X GET "https://api.usmewe.com/v1/webhooks/{webhookId}/deliveries" \
  -H "Authorization: Bearer YOUR_API_KEY"