home / skills / venkateshvenki404224 / frappe-apps-manager / frappe-webhook-manager

This skill helps you create secure webhook receivers and senders for Frappe integrations, enabling reliable event-driven communication with external systems.

npx playbooks add skill venkateshvenki404224/frappe-apps-manager --skill frappe-webhook-manager

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
3.1 KB
---
name: frappe-webhook-manager
description: Create webhook handlers for Frappe integrations. Use when implementing webhooks, event-driven integrations, or external system notifications.
---

# Frappe Webhook Manager

Generate secure webhook receivers and senders for Frappe integrations with external systems.

## When to Use This Skill

Claude should invoke this skill when:
- User wants to receive webhooks from external services
- User needs to send webhooks to external systems
- User mentions webhook, event-driven integration, or external notifications
- User wants to integrate payment gateways, APIs, or third-party services
- User needs to handle real-time events from external systems

## Capabilities

### 1. Webhook Receiver

**Secure Webhook Endpoint:**
```python
import frappe
import hmac
import hashlib

@frappe.whitelist(allow_guest=True)
def webhook_receiver():
    """Receive webhook from external service"""
    # Get signature
    signature = frappe.get_request_header('X-Webhook-Signature')

    # Verify signature
    secret = frappe.conf.get('webhook_secret')
    expected = hmac.new(
        secret.encode(),
        frappe.request.data,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(signature, expected):
        frappe.throw(_('Invalid signature'), frappe.AuthenticationError)

    # Parse payload
    payload = frappe.parse_json(frappe.request.data)

    # Process webhook
    event = payload.get('event')
    if event == 'payment.success':
        handle_payment_success(payload)
    elif event == 'customer.updated':
        handle_customer_update(payload)

    return {'status': 'success'}
```

### 2. Outgoing Webhook

**Send Webhook on Document Event:**
```python
class SalesInvoice(Document):
    def on_submit(self):
        # Send webhook on submission
        send_webhook('invoice.submitted', self.as_dict())

def send_webhook(event, data):
    """Send webhook to external system"""
    webhook_url = frappe.conf.get('external_webhook_url')

    payload = {
        'event': event,
        'data': data,
        'timestamp': frappe.utils.now()
    }

    # Enqueue for async processing
    frappe.enqueue(
        '_send_webhook',
        webhook_url=webhook_url,
        payload=payload,
        queue='short'
    )

def _send_webhook(webhook_url, payload):
    """Send webhook with retry logic"""
    import requests

    for attempt in range(3):
        try:
            response = requests.post(
                webhook_url,
                json=payload,
                headers={'Content-Type': 'application/json'},
                timeout=10
            )

            if response.status_code == 200:
                return True
        except Exception as e:
            if attempt == 2:
                frappe.log_error(frappe.get_traceback(),
                    f"Webhook Failed: {webhook_url}")

    return False
```

## References

**Frappe Webhook Implementation:**
- Webhook DocType: https://github.com/frappe/frappe/tree/develop/frappe/integrations/doctype/webhook
- Integration Request: https://github.com/frappe/frappe/tree/develop/frappe/integrations/doctype/integration_request

Overview

This skill generates secure webhook receivers and outgoing webhook senders tailored for Frappe applications. It focuses on signing and verifying payloads, parsing events, and reliable delivery with retry and async processing. Use it to wire Frappe document lifecycle events to external systems or to accept notifications from third-party services. The goal is secure, maintainable, and testable webhook integrations.

How this skill works

The skill creates a guest-accessible webhook receiver that verifies HMAC signatures against a configured secret, parses JSON payloads, and dispatches event-specific handlers. For outgoing webhooks it provides helper functions to build payloads, enqueue asynchronous delivery, and implement retry and error logging. It uses Frappe configuration for secrets and target URLs, and leverages frappe.enqueue for background tasks to avoid blocking web requests.

When to use it

  • Receiving event notifications from payment gateways, CRMs, or SaaS platforms.
  • Sending document lifecycle events (submit, cancel, update) to external systems.
  • Implementing event-driven integrations or webhooks in Frappe apps.
  • Handling real-time updates that must be processed reliably and asynchronously.
  • Securing endpoints with HMAC signatures to prevent spoofed requests.

Best practices

  • Store webhook secrets and external URLs in frappe.conf, not in code or version control.
  • Verify incoming requests using HMAC and constant-time comparison (hmac.compare_digest).
  • Enqueue outbound webhooks for async delivery and implement retry with logging.
  • Keep receivers idempotent: detect and ignore duplicate events to avoid side effects.
  • Validate and sanitize payloads and restrict guest access to only the webhook route.

Example use cases

  • Accept payment.success webhooks, validate signature, and mark invoices paid.
  • Send invoice.submitted events to a fulfillment service when a Sales Invoice is submitted.
  • Forward customer.updated events to a CRM and update local customer records.
  • Integrate with a third-party analytics service by sending order events asynchronously.
  • Build a secure webhook endpoint for a subscription billing provider to update subscription state.

FAQ

How do I store webhook secrets securely?

Keep secrets in frappe.conf or environment variables and avoid committing them to source control.

What if an external service retries the same webhook?

Design handlers to be idempotent and record processed event IDs so duplicate deliveries are ignored.