Payment Webhooks
Receive real-time notifications when payments are created, completed, refunded, or fail. Configure webhook endpoints and verify event signatures.
Muin sends webhook events to your server when payment-related events occur. Use webhooks to trigger fulfillment, update your own systems, or sync records in real time.
Setting Up a Webhook
- Go to Settings → Integrations → Webhooks
- Click Add Endpoint
- Enter your endpoint URL (must be publicly accessible HTTPS)
- Select the event types you want to receive
- Click Save: Muin immediately sends a test ping to verify the endpoint is reachable
Payment Event Types
| Event | Fires when |
|---|---|
payment.created |
A new payment record is created |
payment.completed |
Payment is fully settled |
payment.failed |
Payment attempt fails (card declined, insufficient funds, etc.) |
payment.refunded |
A full or partial refund is issued |
payment.disputed |
A chargeback or dispute is opened |
payment_page.viewed |
A payment page receives a visit |
donation.received |
A new donation is processed |
pledge.fulfilled |
A pledge payment completes |
subscription.created |
A recurring subscription is set up |
subscription.renewed |
A recurring billing cycle succeeds |
subscription.cancelled |
A subscription is cancelled |
subscription.payment_failed |
A recurring billing cycle fails |
Webhook Payload
Each event is delivered as an HTTPS POST with a JSON body:
{
"event": "payment.completed",
"id": "evt_01HXK2...",
"created_at": "2026-06-08T14:23:11Z",
"tenant_id": "ten_01HXK...",
"data": {
"payment_id": "pay_01HXK...",
"amount_cents": 5000,
"currency": "USD",
"method": "card",
"status": "completed",
"contact_id": "con_01HXK...",
"invoice_id": "inv_01HXK...",
"metadata": {}
}
}
Verifying Webhook Signatures
Every webhook delivery includes a Muin-Signature header. Verify it to confirm the request came from Muin and hasn’t been tampered with:
- Get your endpoint’s Signing Secret from Settings → Integrations → Webhooks → [your endpoint] → Signing Secret
- Compute
HMAC-SHA256(raw_body, signing_secret) - Compare to the value in
Muin-Signatureheader
Python example:
import hmac, hashlib
def verify(payload_bytes, signature_header, secret):
expected = hmac.new(secret.encode(), payload_bytes, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header)
Reject any delivery where verification fails.
Retries
If your endpoint returns a non-2xx response (or times out after 10 seconds), Muin retries delivery:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 5 minutes |
| 3 | 30 minutes |
| 4 | 2 hours |
| 5 | 6 hours |
After 5 failed attempts, the delivery is marked Failed in the webhook log. Failed events can be manually replayed from the endpoint detail view.
Webhook Logs
Every delivery attempt is logged in Settings → Integrations → Webhooks → [endpoint] → Logs:
- Request payload
- Response status and body
- Delivery timestamp
- Success / failure status
Use the log to debug integrations without waiting for events to recur.
The inbound provider log is a different list
The log above covers webhooks Muin sends you. For webhooks your payment provider sends Muin, open Smart Payments → Settings → Webhook log. It lists each event received, with:
- the provider
- the event ID
- whether the signature was Verified or Rejected
- the payload
This is where to look when a payment happened at the provider but never appeared in Muin. A Rejected signature means Muin discarded the event because it could not verify it came from your provider, which is the correct behavior. If it is happening to legitimate events, reconnect the provider under Settings → Stripe Connect so the signing secret is refreshed.
Next Steps
- Payment Transactions: view all payment records
- Payment Pages: create public-facing payment links
- Payment Settings: the inbound provider Webhook log and the Stripe Connect tab