If you’re using ERPNext v15 and want Slack notifications when stock movements happen, this guide walks you through:
- Sending Slack alerts for:
- Material Issue
- Material Transfer
- Purchase Receipt
- Avoiding the common Jinja error:
TypeError: 'builtin_function_or_method' object is not iterable
This method uses ERPNext’s built-in Webhook DocType and Slack Incoming Webhooks.
Step 1 — Create a Slack Incoming Webhook
- Go to https://api.slack.com/apps
- Create a new app (or use an existing one).
- Enable Incoming Webhooks.
- Click Add New Webhook to Workspace.
- Choose your channel.
- Copy the generated Webhook URL.
Important:
Treat this URL like a password. Do not publish or share it publicly.
Step 2 — Create Webhook for Stock Entry (Material Issue + Transfer)
In ERPNext:
Go to:
Search → Webhook → New
Configure:
Document Type:
Stock Entry
Doc Event:
on_submit
Request Method:
POST
Request URL:
Paste your Slack webhook URL
Enabled:
Checked
Add Condition (Important)
Only trigger for Material Issue and Material Transfer:
doc.stock_entry_type in ("Material Issue", "Material Transfer")
Why this matters:
Stock Entry is one DocType, but it has multiple types. This prevents Slack spam from other stock movements.
Add Header
Add one row:
| Key | Value |
|---|---|
| Content-Type | application/json |
Step 3 — Correct JSON Payload (Avoid the Common Error)
Many users encounter this error:
TypeError: 'builtin_function_or_method' object is not iterable
This happens because in Webhooks, doc is treated as a dictionary.
Using doc.items conflicts with Python’s dict.items() method.
❌ Wrong (causes error)
{% for i in doc.items %}
✅ Correct
{% for i in doc.get('items', []) %}
Use This Working JSON Payload (Stock Entry)
{
"text": "📦 *Stock Entry Submitted*\n*Type:* {{ doc.get('stock_entry_type', '—') }}\n*ID:* {{ doc.get('name', '—') }}\n*Company:* {{ doc.get('company', '—') }}\n*Posting:* {{ doc.get('posting_date', '—') }} {{ doc.get('posting_time', '') }}\n*From WH:* {{ doc.get('from_warehouse') or '—' }}\n*To WH:* {{ doc.get('to_warehouse') or '—' }}\n\n*Items:*\n{% for i in doc.get('items', []) %}• {{ i.get('item_code', '—') }} — Qty: {{ i.get('qty', 0) }} {{ i.get('uom', '') }} ({{ i.get('s_warehouse') or i.get('t_warehouse') or '—' }})\n{% endfor %}"
}
Step 4 — Create Separate Webhook for Purchase Receipt
Important:
Purchase Receipt is NOT a Stock Entry Type.
It is a separate DocType.
So you must create a second webhook.
Create New Webhook
Document Type:
Purchase Receipt
Doc Event:
on_submit
Request Method:
POST
Request URL:
Slack webhook URL
Header:
Content-Type: application/json
No condition required unless you want to filter by supplier or company.
Working JSON Payload (Purchase Receipt)
{
"text": "🧾 *Purchase Receipt Submitted*\n*ID:* {{ doc.get('name', '—') }}\n*Supplier:* {{ doc.get('supplier', '—') }}\n*Company:* {{ doc.get('company', '—') }}\n*Posting:* {{ doc.get('posting_date', '—') }} {{ doc.get('posting_time', '') }}\n\n*Items Received:*\n{% for i in doc.get('items', []) %}• {{ i.get('item_code', '—') }} — Qty: {{ i.get('qty', 0) }} {{ i.get('uom', '') }} ({{ i.get('warehouse') or '—' }})\n{% endfor %}"
}
Step 5 — Testing
- Create a Material Transfer.
- Submit it.
- Check Slack channel.
Then test:
- Material Issue
- Purchase Receipt
If nothing appears:
Check Logs (Docker)
docker compose logs -f backend
Or inside container:
bench --site yoursite tail
Optional Enhancement — Add Clickable ERP Link
If your ERP URL is:
https://erp.yourdomain.com
You can add this inside your Slack message:
https://erp.yourdomain.com/app/stock-entry/{{ doc.get('name') }}
Slack will auto-link it.
Why This Fix Works
In Webhooks:
docbehaves as a Python dictionary.doc.itemsconflicts with dictionary method.items().- Using
doc.get('items', [])safely retrieves the child table list.
This prevents:
TypeError: 'builtin_function_or_method' object is not iterable
Final Result
You now have Slack alerts for:
- Material Issue
- Material Transfer
- Purchase Receipt
Triggered automatically on submit, with item-level detail
Leave a Reply