Skip to main content

Webhooks

Webhooks let you receive real-time HTTP callbacks when events occur on your WhatsApp instances. Instead of polling the API, Wappfy pushes events to your server as they happen.

Supported Events

Wappfy supports 12 webhook event types:
EventDescription
message.receivedA new inbound message was received.
message.sentAn outbound message was successfully sent.
message.deliveredA sent message was delivered to the recipient’s device (double check mark).
message.readA sent message was read by the recipient (blue check marks).
message.failedAn outbound message failed to send.
message.reactionSomeone reacted to a message with an emoji.
EventDescription
instance.connectedAn instance successfully connected to WhatsApp.
instance.disconnectedAn instance lost its WhatsApp connection.
instance.qrA new QR code is available for scanning.
EventDescription
group.joinedA participant joined a group (including the bot itself).
group.leftA participant left a group.
contact.createdA new contact was saved or detected.

Create a Webhook

Register a webhook endpoint to start receiving events.
curl -X POST https://api.wappfy.io/api/webhooks \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/wappfy",
    "events": ["message.received", "message.sent", "message.delivered"],
    "instance_id": "inst_abc123",
    "secret": "whsec_my_signing_secret",
    "retry_count": 3,
    "timeout_ms": 10000
  }'
Response:
{
  "data": {
    "id": "wh_xyz789",
    "url": "https://your-server.com/webhooks/wappfy",
    "events": ["message.received", "message.sent", "message.delivered"],
    "instance_id": "inst_abc123",
    "is_active": true,
    "retry_count": 3,
    "timeout_ms": 10000,
    "created_at": "2026-02-10T12:00:00Z"
  }
}

Configuration Options

FieldTypeDefaultDescription
urlstringrequiredThe HTTPS URL that will receive webhook POST requests.
eventsstring[]requiredArray of event types to subscribe to.
instance_idstringnullScope the webhook to a specific instance. If null, receives events from all instances.
secretstringnullSecret used to generate HMAC signatures for payload verification.
retry_countnumber3Number of retry attempts on delivery failure (0-5).
timeout_msnumber10000Request timeout in milliseconds (1000-30000).
The instance_id field is optional. If omitted, the webhook will receive events from all instances in your account.

List Webhooks

curl https://api.wappfy.io/api/webhooks \
  -H "X-Api-Key: YOUR_API_KEY"
Response:
{
  "data": [
    {
      "id": "wh_xyz789",
      "url": "https://your-server.com/webhooks/wappfy",
      "events": ["message.received", "message.sent", "message.delivered"],
      "instance_id": "inst_abc123",
      "is_active": true,
      "retry_count": 3,
      "timeout_ms": 10000
    }
  ]
}

Update a Webhook

Update the URL, events, or configuration of an existing webhook.
curl -X PATCH https://api.wappfy.io/api/webhooks/wh_xyz789 \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["message.received", "message.sent", "message.delivered", "message.read"],
    "is_active": true
  }'

Delete a Webhook

curl -X DELETE https://api.wappfy.io/api/webhooks/wh_xyz789 \
  -H "X-Api-Key: YOUR_API_KEY"

Delivery Payload Format

When an event occurs, Wappfy sends a POST request to your webhook URL with the following structure:
{
  "id": "dlv_abc123def456",
  "event": "message.received",
  "instance_id": "inst_abc123",
  "timestamp": "2026-02-10T14:30:00Z",
  "data": {
    "message_id": "BAE5F2C4D3B2A1",
    "chat_id": "[email protected]",
    "from": "[email protected]",
    "type": "text",
    "text": "Hello!",
    "timestamp": "2026-02-10T14:30:00Z"
  }
}

Payload Fields

FieldDescription
idUnique delivery ID. Use this for deduplication.
eventThe event type that triggered this delivery.
instance_idThe instance that generated the event.
timestampISO 8601 timestamp of when the event occurred.
dataEvent-specific payload. Contents vary by event type.

HMAC Signature Verification

If you provide a secret when creating a webhook, every delivery will include an X-Wappfy-Signature header containing an HMAC-SHA256 signature of the request body. Always verify this signature to ensure the request came from Wappfy and was not tampered with.

Verification Examples

const crypto = require("crypto");

function verifyWebhookSignature(req, secret) {
  const signature = req.headers["x-wappfy-signature"];
  if (!signature) return false;

  const expectedSignature = crypto
    .createHmac("sha256", secret)
    .update(JSON.stringify(req.body))
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Express middleware
app.post("/webhooks/wappfy", (req, res) => {
  const isValid = verifyWebhookSignature(req, "whsec_my_signing_secret");

  if (!isValid) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const { event, data } = req.body;
  console.log(`Received event: ${event}`, data);

  // Always respond with 200 quickly to prevent retries
  res.status(200).json({ received: true });
});
Always use constant-time comparison (like timingSafeEqual or hmac.compare_digest) when verifying signatures to prevent timing attacks.

Retry Behavior

If your server does not respond with a 2xx status code within the configured timeout_ms, Wappfy will retry the delivery.
AttemptDelay
1st retry10 seconds
2nd retry60 seconds
3rd retry5 minutes
4th retry30 minutes
5th retry2 hours
Retries stop when a 2xx response is received or the retry_count is exhausted. The default retry count is 3.

Viewing Delivery History

Check the delivery log for a webhook to see past delivery attempts and their results.
curl https://api.wappfy.io/api/webhooks/wh_xyz789/deliveries \
  -H "X-Api-Key: YOUR_API_KEY"
Response:
{
  "data": [
    {
      "id": "dlv_abc123def456",
      "event": "message.received",
      "status": "delivered",
      "http_status": 200,
      "attempts": 1,
      "created_at": "2026-02-10T14:30:00Z",
      "delivered_at": "2026-02-10T14:30:01Z"
    },
    {
      "id": "dlv_ghi789jkl012",
      "event": "message.sent",
      "status": "failed",
      "http_status": 500,
      "attempts": 3,
      "created_at": "2026-02-10T14:31:00Z",
      "last_error": "Server returned 500 Internal Server Error"
    }
  ]
}

Event Payload Examples

{
  "id": "dlv_abc123",
  "event": "message.received",
  "instance_id": "inst_abc123",
  "timestamp": "2026-02-10T14:30:00Z",
  "data": {
    "message_id": "BAE5F2C4D3B2A1",
    "chat_id": "[email protected]",
    "from": "[email protected]",
    "type": "text",
    "text": "Hello, I need help with my order",
    "timestamp": "2026-02-10T14:30:00Z"
  }
}
{
  "id": "dlv_def456",
  "event": "message.delivered",
  "instance_id": "inst_abc123",
  "timestamp": "2026-02-10T14:30:05Z",
  "data": {
    "message_id": "BAE5A1B2C3D4E5",
    "chat_id": "[email protected]",
    "status": "delivered"
  }
}
{
  "id": "dlv_ghi789",
  "event": "message.read",
  "instance_id": "inst_abc123",
  "timestamp": "2026-02-10T14:31:00Z",
  "data": {
    "message_id": "BAE5A1B2C3D4E5",
    "chat_id": "[email protected]",
    "status": "read"
  }
}
{
  "id": "dlv_jkl012",
  "event": "message.reaction",
  "instance_id": "inst_abc123",
  "timestamp": "2026-02-10T14:32:00Z",
  "data": {
    "message_id": "BAE5F2C4D3B2A1",
    "chat_id": "[email protected]",
    "from": "[email protected]",
    "reaction": "\u2764\ufe0f"
  }
}
{
  "id": "dlv_mno345",
  "event": "instance.connected",
  "instance_id": "inst_abc123",
  "timestamp": "2026-02-10T12:00:00Z",
  "data": {
    "instance_id": "inst_abc123",
    "status": "connected",
    "phone_number": "5511999998888"
  }
}
{
  "id": "dlv_pqr678",
  "event": "instance.qr",
  "instance_id": "inst_abc123",
  "timestamp": "2026-02-10T11:59:00Z",
  "data": {
    "instance_id": "inst_abc123",
    "qr": "data:image/png;base64,iVBORw0KGgo..."
  }
}
{
  "id": "dlv_stu901",
  "event": "group.joined",
  "instance_id": "inst_abc123",
  "timestamp": "2026-02-10T15:00:00Z",
  "data": {
    "group_id": "[email protected]",
    "participant": "[email protected]"
  }
}

Best Practices

Respond quickly

Return a 200 status within 5 seconds. Process the event asynchronously to avoid timeouts.

Deduplicate

Use the delivery id to detect and skip duplicate deliveries caused by retries.

Verify signatures

Always validate the X-Wappfy-Signature header if you configured a secret.

Use HTTPS

Webhook URLs must use HTTPS. HTTP endpoints will be rejected.