Back to skills
SkillHub ClubShip Full StackFull Stack

dooray-hook

Send automated notifications to Dooray! messenger channels via webhooks.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
3,127
Hot score
99
Updated
March 20, 2026
Overall rating
C4.0
Composite score
4.0
Best-practice grade
B70.4

Install command

npx @skill-hub/cli install openclaw-skills-openclaw-dooray-hook-skill

Repository

openclaw/skills

Skill path: skills/iizs/openclaw-dooray-hook-skill

Send automated notifications to Dooray! messenger channels via webhooks.

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: openclaw.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install dooray-hook into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/openclaw/skills before adding dooray-hook to shared team environments
  • Use dooray-hook for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: dooray-hook
description: Send automated notifications to Dooray! messenger channels via webhooks.
homepage: https://dooray.com
metadata:
  openclaw:
    emoji: "πŸ“¨"
    requires:
      bins: ["python3"]
      config: ["skills.entries.dooray-hook.config"]
---

# Dooray! Webhook Skill

A seamless integration to send text notifications and status updates to **Dooray!** chat rooms using Incoming Webhooks.

## Overview

This skill allows OpenClaw to communicate with your team on Dooray!. It supports multiple chat rooms, customizable bot profiles, and configurable SSL verification settings.

## Configuration

To use this skill, you must define your Dooray! webhook URLs in the OpenClaw global config (`~/.openclaw/openclaw.json`).

> **Security Note:** Webhook URLs are stored in your local config file. Ensure this file's permissions are restricted (e.g., `chmod 600`).

```json
{
  "skills": {
    "entries": {
      "dooray-hook": {
        "enabled": true,
        "config": {
          "botName": "N.I.C.K.",
          "botIconImage": "[https://static.dooray.com/static_images/dooray-bot.png](https://static.dooray.com/static_images/dooray-bot.png)",
          "verify_ssl": true,
          "rooms": {
            "General": "[https://hook.dooray.com/services/YOUR_TOKEN_1](https://hook.dooray.com/services/YOUR_TOKEN_1)",
            "Alerts": "[https://hook.dooray.com/services/YOUR_TOKEN_2](https://hook.dooray.com/services/YOUR_TOKEN_2)"
          }
        }
      }
    }
  }
}

```

### Configuration Options

* **`rooms`** (Required): A dictionary mapping room names to webhook URLs.
* **`botName`** (Optional): The name displayed for the bot message (Default: "OpenClaw").
* **`verify_ssl`** (Optional): Set to `false` to disable SSL certificate verification. Useful for corporate proxies or self-signed certificates. (Default: `true`).

## Usage

### πŸ’¬ Natural Language

You can ask OpenClaw to send messages directly:

* *"Send 'Server deployment successful' to the Alerts room on Dooray."*
* *"Tell the General channel that I'll be late for the meeting."*

### πŸ’» CLI Execution

```bash
python scripts/send_dooray.py "RoomName" "Your message content"

```

## Technical Details

* **Zero Dependencies**: Uses Python's built-in `urllib.request` and `json` modules. No `pip install` or `venv` required.
* **Security**:
* Defaults to secure SSL context (`verify_ssl: true`).
* Requires explicit configuration to bypass certificate checks.



## Troubleshooting

* **[SSL: CERTIFICATE_VERIFY_FAILED]**: If you are behind a corporate proxy or using self-signed certificates, add `"verify_ssl": false` to your config.
* **Room Not Found**: Ensure the room name matches the key in your `openclaw.json` exactly (case-sensitive).
* **Invalid URL**: Verify the webhook URL starts with `https://hook.dooray.com/services/`.

```

```

---

## Referenced Files

> The following files are referenced in this skill and included for context.

### scripts/send_dooray.py

```python
#!/usr/bin/env python3
"""
Send a message to a Dooray chat room via incoming webhook.
Uses only Python standard library (no external dependencies).

Usage:
    python send_dooray.py "RoomName" "Message text"
    python send_dooray.py --list

Examples:
    python send_dooray.py "Dev Team" "Deployment complete βœ…"
    python send_dooray.py --list  # List configured rooms
"""

import json
import os
import sys
import ssl
import urllib.request
import urllib.error
from pathlib import Path


def load_config():
    """Load OpenClaw config from ~/.openclaw/openclaw.json"""
    config_path = Path.home() / ".openclaw" / "openclaw.json"
    
    if not config_path.exists():
        print(f"Error: OpenClaw config not found at {config_path}", file=sys.stderr)
        print("Ensure OpenClaw is configured and the gateway has written the config.", file=sys.stderr)
        sys.exit(1)
    
    try:
        with open(config_path, 'r', encoding='utf-8') as f:
            config = json.load(f)
        return config
    except json.JSONDecodeError as e:
        print(f"Error: Failed to parse OpenClaw config: {e}", file=sys.stderr)
        sys.exit(1)
    except Exception as e:
        print(f"Error: Failed to read OpenClaw config: {e}", file=sys.stderr)
        sys.exit(1)


def get_dooray_config(config):
    """Extract Dooray skill config from OpenClaw config"""
    try:
        # Strict matching: Only look for 'dooray-hook' as defined in SKILL.md
        entries = config.get("skills", {}).get("entries", {})
        dooray_config = entries.get("dooray-hook", {}).get("config", {})
        
        # Return empty dict if not found, to be handled by caller
        return dooray_config
    except (KeyError, AttributeError):
        print("Error: Dooray skill config structure is invalid", file=sys.stderr)
        sys.exit(1)


def list_rooms(dooray_config):
    """List all configured Dooray rooms"""
    rooms = dooray_config.get("rooms", {})
    
    if not rooms:
        print("No Dooray rooms configured or 'dooray-hook' entry is missing.")
        print("\nPlease add the configuration to ~/.openclaw/openclaw.json:")
        print('  "skills": {')
        print('    "entries": {')
        print('      "dooray-hook": {')
        print('        "config": {')
        print('          "verify_ssl": true,')
        print('          "rooms": { "RoomName": "https://hook.dooray.com/..." }')
        print('        }')
        print('      }')
        print('    }')
        print('  }')
        return
    
    print("Configured Dooray rooms:")
    for room_name in sorted(rooms.keys()):
        webhook_url = rooms[room_name]
        # Mask webhook token for security
        masked_url = webhook_url[:40] + "..." if len(webhook_url) > 40 else webhook_url
        print(f"  - {room_name}: {masked_url}")


def send_message(room_name, message_text, dooray_config):
    """Send a message to a Dooray chat room"""
    rooms = dooray_config.get("rooms", {})
    
    if not rooms:
         print(f"Error: No rooms configured under 'dooray-hook'.", file=sys.stderr)
         sys.exit(1)

    if room_name not in rooms:
        print(f"Error: Room '{room_name}' not found in 'dooray-hook' config", file=sys.stderr)
        print(f"\nAvailable rooms: {', '.join(sorted(rooms.keys()))}", file=sys.stderr)
        sys.exit(1)
    
    webhook_url = rooms[room_name]
    bot_name = dooray_config.get("botName", "OpenClaw")
    bot_icon = dooray_config.get("botIconImage", "https://static.dooray.com/static_images/dooray-bot.png")
    
    # Check for SSL verification override (Default: True)
    verify_ssl = dooray_config.get("verify_ssl", True)

    # Prepare payload
    payload = {
        "botName": bot_name,
        "botIconImage": bot_icon,
        "text": message_text
    }
    
    payload_json = json.dumps(payload).encode('utf-8')
    
    # Send POST request
    try:
        req = urllib.request.Request(
            webhook_url,
            data=payload_json,
            headers={
                'Content-Type': 'application/json',
                'User-Agent': 'OpenClaw-Dooray-Skill/1.0'
            },
            method='POST'
        )
        
        # [SECURITY] Conditional SSL Context
        if verify_ssl:
            # Secure default: Validates SSL certificates
            ssl_context = ssl.create_default_context()
        else:
            # Insecure opt-in: Ignores certificate errors (for proxies/self-signed certs)
            # This handles the [SSL: CERTIFICATE_VERIFY_FAILED] error if config allows it.
            ssl_context = ssl._create_unverified_context()

        with urllib.request.urlopen(req, timeout=10, context=ssl_context) as response:
            status_code = response.getcode()
            response_body = response.read().decode('utf-8')
            
            if status_code == 200:
                print(f"βœ… Message sent to Dooray room '{room_name}'")
                return True
            else:
                print(f"⚠️  Unexpected response: {status_code}", file=sys.stderr)
                print(f"Response: {response_body}", file=sys.stderr)
                return False
    
    except urllib.error.HTTPError as e:
        print(f"❌ HTTP Error {e.code}: {e.reason}", file=sys.stderr)
        try:
            error_body = e.read().decode('utf-8')
            print(f"Response: {error_body}", file=sys.stderr)
        except:
            pass
        return False
    
    except urllib.error.URLError as e:
        print(f"❌ Network error: {e.reason}", file=sys.stderr)
        if "CERTIFICATE_VERIFY_FAILED" in str(e.reason):
             print("\nπŸ’‘ Hint: Try setting 'verify_ssl': false in your config if using a proxy or self-signed cert.", file=sys.stderr)
        return False
    
    except Exception as e:
        print(f"❌ Unexpected error: {e}", file=sys.stderr)
        return False


def main():
    """Main entry point"""
    if len(sys.argv) < 2:
        print(__doc__, file=sys.stderr)
        sys.exit(1)
    
    # Load OpenClaw config
    config = load_config()
    dooray_config = get_dooray_config(config)
    
    # Handle --list flag
    if sys.argv[1] == "--list":
        list_rooms(dooray_config)
        sys.exit(0)
    
    # Send message
    if len(sys.argv) < 3:
        print("Error: Missing arguments", file=sys.stderr)
        print(__doc__, file=sys.stderr)
        sys.exit(1)
    
    room_name = sys.argv[1]
    message_text = sys.argv[2]
    
    success = send_message(room_name, message_text, dooray_config)
    sys.exit(0 if success else 1)


if __name__ == "__main__":
    main()
```



---

## Skill Companion Files

> Additional files collected from the skill directory layout.

### _meta.json

```json
{
  "owner": "iizs",
  "slug": "openclaw-dooray-hook-skill",
  "displayName": "Dooray Hook",
  "latest": {
    "version": "0.1.1",
    "publishedAt": 1770876083042,
    "commit": "https://github.com/openclaw/skills/commit/edc709a15fb62e6a718731c01cad24a2d6420617"
  },
  "history": [
    {
      "version": "0.1.0",
      "publishedAt": 1770795251088,
      "commit": "https://github.com/openclaw/skills/commit/1b65261cf904319770dede4d4606513312ca7495"
    }
  ]
}

```

### references/dooray-api.md

```markdown
# Dooray Incoming Webhook API Reference

## Overview

Dooray provides incoming webhooks for sending messages to chat rooms programmatically. Each webhook is unique to a specific chat room.

## Getting a Webhook URL

1. Open the target Dooray chat room
2. Go to **Settings** β†’ **Integrations** β†’ **Incoming Webhook**
3. Enable incoming webhook
4. Copy the generated webhook URL

The URL format is:
```
https://hook.dooray.com/services/{TOKEN}
```

## HTTP Request

**Method:** POST  
**URL:** `https://hook.dooray.com/services/{TOKEN}`  
**Content-Type:** `application/json`

## Request Body

### Required Fields

```json
{
  "text": "Your message here"
}
```

### Optional Fields

```json
{
  "botName": "MyBot",
  "botIconImage": "https://static.dooray.com/static_images/dooray-bot.png",
  "text": "Dooray!"
}
```

#### Field Descriptions

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `text` | string | Yes | Message content (plain text only) |
| `botName` | string | No | Display name for the bot sender |
| `botIconImage` | string | No | Avatar image URL for the bot |

## Response

### Success Response

**HTTP Status:** 200 OK

```json
{
  "success": true
}
```

### Error Responses

**HTTP Status:** 400 Bad Request
- Invalid JSON payload
- Missing required fields

**HTTP Status:** 404 Not Found
- Invalid webhook token
- Webhook has been deleted

**HTTP Status:** 429 Too Many Requests
- Rate limit exceeded

## Limitations

### Content Limitations

- **Markdown:** Not supported
- **Text Length:** Maximum 4000 characters (recommended)
- **Formatting:** Plain text only
- **Mentions:** Not supported via webhook

### Rate Limits

- Recommended: Max 10 messages per minute per webhook
- Excessive requests may be throttled or blocked

### Security

- Webhook URLs should be treated as secrets
- Anyone with the URL can post to the room
- No authentication required (URL itself is the credential)
- Regenerate webhook if URL is compromised

## Example Requests

### cURL Example

```bash
curl -X POST https://hook.dooray.com/services/YOUR_TOKEN \
  -H "Content-Type: application/json" \
  -d '{
    "botName": "DeployBot",
    "botIconImage": "https://example.com/bot-icon.png",
    "text": "Deployment to production completed successfully βœ…"
  }'
```

### Python (urllib) Example

```python
import urllib.request
import json

webhook_url = "https://hook.dooray.com/services/YOUR_TOKEN"
payload = {
    "botName": "AlertBot",
    "text": "Server CPU usage is high!"
}

req = urllib.request.Request(
    webhook_url,
    data=json.dumps(payload).encode('utf-8'),
    headers={'Content-Type': 'application/json'}
)

with urllib.request.urlopen(req) as response:
    print(response.read().decode('utf-8'))
```

### JavaScript (fetch) Example

```javascript
const webhookUrl = 'https://hook.dooray.com/services/YOUR_TOKEN';
const payload = {
  botName: 'StatusBot',
  text: 'All systems operational 🟒'
};

fetch(webhookUrl, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload)
})
  .then(response => response.json())
  .then(data => console.log(data));
```

## Best Practices

### Message Design

1. **Keep messages concise** - Aim for clarity over verbosity
2. **Use emoji sparingly** - βœ… ❌ 🟒 for status indicators
3. **Include context** - Avoid ambiguous messages
4. **Structure information** - Use line breaks for readability

### Error Handling

1. **Retry on failure** - Implement exponential backoff
2. **Log failures** - Track webhook delivery issues
3. **Validate URLs** - Check webhook format before sending
4. **Handle rate limits** - Queue messages if needed

### Security

1. **Store URLs securely** - Never commit webhooks to version control
2. **Use environment variables** - Keep tokens out of code
3. **Rotate webhooks** - Regenerate if exposed or periodically
4. **Limit access** - Only share webhooks with authorized systems

## Troubleshooting

### Message Not Appearing

- Verify webhook URL is correct
- Check that webhook is still enabled in Dooray
- Ensure JSON payload is valid
- Confirm room still exists

### 404 Error

- Webhook may have been deleted/regenerated
- Token in URL is invalid
- Room was archived or deleted

### Rate Limiting

- Reduce message frequency
- Implement message batching
- Add delays between requests

## Official Documentation

For the latest official Dooray webhook documentation (Korean):
https://helpdesk.dooray.com/

## Change Log

- **2024-02-10**: Initial API reference for OpenClaw skill

```

dooray-hook | SkillHub