Webhooks API

Webhooks API Reference

Receive real-time notifications about events in your Waffy integration. Configure secure webhook endpoints to stay updated on payments, contracts, and user activities.

Overview

Webhooks allow your application to receive real-time notifications when events occur in your Waffy integration. Instead of polling our API, webhooks push event data to your endpoints immediately when something happens.

Real-time

Instant notifications when events occur

Secure

HMAC signature verification and HTTPS required

Reliable

Automatic retries with exponential backoff

PATCHConfigure Webhook

Set up or update your webhook endpoint to receive contract status notifications

Endpoint

PATCH https://auth.waffyapp.com/api/clients/{merchant_client_id}

This is production. In the sandbox and in staging use that environment's authentication host (dev-auth.waffyapp.com or auth-stg.waffyapp.com) with a client created there.

Who can call it

Your organization's client admin, with the client admin token (Authorization: Bearer ...), and only for the client that token was issued for: merchant_client_id must be that client. Waffy staff can set it for any client. The org token (client_credentials) is refused with 403. Waffy does not validate the URL when it is saved: use a public HTTPS endpoint.

Request Body

{
  "webhookUrl": "https://example.webhook.com"
}

Response (200 OK)

{
  "status": 204,
  "timestamp": "2026-01-25T12:00:00.000000"
}

Note: the HTTP status is 200. The 204 is the code the service puts in the body.

Webhook Payload Structure

Example of how webhook events are delivered to your endpoint

Contract Status Update Event

{
  "contractId": "697107b674385b2c31672af5",
  "status": "PAID",
  "referenceId": "txn_9i8h7g6f5e4d"
}
Field Descriptions:
  • contractId - The milestone ID (contract ID of the milestone)
  • status - Current contract status (see status values below)
  • referenceId - Reference ID for the transaction (if applicable)

Contract Status Values

CREATED
Initial status when contract is first created
PAID
Payment has been verified and funds are secured
CASHOUT_IN_PROGRESS
Money is being released to parties
COMPLETED
Contract is fully completed and all payments processed

Webhook Security

Verify webhook authenticity with HMAC signatures

Signature Verification

Waffy signs webhook payloads with your webhook secret using HMAC-SHA256. The signature is included in the Waffy-Signature header.

// Node.js example
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');
    
  const expectedHeader = `sha256=${expectedSignature}`;
  
  return crypto.timingSafeEqual(
    Buffer.from(signature, 'utf8'),
    Buffer.from(expectedHeader, 'utf8')
  );
}

// Usage in Express.js
app.post('/webhooks/waffy', express.raw({type: 'application/json'}), (req, res) => {
  const payload = req.body;
  const signature = req.headers['waffy-signature'];
  const secret = process.env.WAFFY_WEBHOOK_SECRET;
  
  if (!verifyWebhookSignature(payload, signature, secret)) {
    return res.status(401).send('Invalid signature');
  }
  
  const event = JSON.parse(payload);
  
  // Process the webhook event
  switch (event.status) {
    case 'PAID':
      handleContractPaid(event);
      break;
    case 'CASHOUT_IN_PROGRESS':
      handleCashoutInProgress(event);
      break;
    case 'COMPLETED':
      handleContractCompleted(event);
      break;
    // Handle other status types...
  }
  
  res.status(200).send('OK');
});

Delivery & Retry Policy

Retry Policy

• Webhooks are retried up to 3 times on failure

• Exponential backoff: 1s, 10s, 100s delays

• HTTP 200-299 responses are considered successful

• Timeout after 30 seconds

Best Practices

• Respond with HTTP 200 status code quickly

• Implement idempotency using the event ID

• Always verify the webhook signature

• Process webhooks asynchronously when possible

Common Errors

Configure Webhook: 400 and 403

A token without the client admin authority is refused with 403. Every other failure is HTTP 400 with an error message: a blank webhookUrl, a client id that is not the token's client, or a client id that does not exist. The URL itself is not checked when it is saved.

422 Unprocessable Entity - Invalid Events

{
  "success": false,
  "error": {
    "code": "INVALID_EVENTS",
    "message": "One or more event types are not supported",
    "details": {
      "invalid_events": ["payment.refunded", "user.deleted"],
      "supported_events": ["payment.completed", "payment.failed", "..."]
    }
  }
}

Webhook Testing Tool

Test your webhook endpoint with sample events

Webhook Guide

Complete guide to implementing webhooks