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.
Install command
npx @skill-hub/cli install openclaw-skills-openclaw-dooray-hook-skill
Repository
Skill path: skills/iizs/openclaw-dooray-hook-skill
Send automated notifications to Dooray! messenger channels via webhooks.
Open repositoryBest 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
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
```