Building billing workflows manually is one of the most error-prone areas of software engineering. Payeny exposes a clean REST API and a real-time event pipeline designed to bridge client agreements directly to automated provisioning.
1. System Architecture Overview
Payeny follows an event-driven architecture. Whenever a client executes a state-changing action—such as viewing a proposal, drawing a signature, or completing checkout—an event is dispatched to your registered endpoints:
↓
[Payeny Core Engine verifies signature & generates invoice]
↓
[Event Dispatcher fires "contract.executed" & "invoice.created"]
↓
[Your App Webhook Endpoint validates HMAC signature]
↓
[Automated Provisioning: Creates repo, invites client to Slack, alerts team]
2. Core Billing Lifecycle Events
| Event Type | Trigger Condition | Common Automation Action |
|---|---|---|
proposal.signed |
Client accepted proposal pricing tier | Notify sales lead, prepare contract |
contract.executed |
All parties submitted valid eSignatures | Create project repository, generate kickoff ticket |
invoice.paid |
Payment gateway settled funds | Unlock deliverable lockbox, provision client space |
deliverable.unlocked |
Client downloaded paid file assets | Send satisfaction survey, log milestone |
3. Verifying HMAC-SHA256 Signatures
Every outbound Payeny webhook includes a secure signature header: X-Payeny-Signature. You must always verify this cryptographic signature against your endpoint webhook secret before trusting the incoming payload.
4. Complete Webhook Handler (Node.js & PHP)
Node.js / Express Example:
const crypto = require('crypto');
const express = require('express');
const app = express();
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-payeny-signature'];
const secret = process.env.PAYENY_WEBHOOK_SECRET;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(req.body)
.digest('hex');
if (signature !== expectedSignature) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body.toString());
switch (event.type) {
case 'invoice.paid':
const invoice = event.data;
console.log(`Payment confirmed for invoice ${invoice.id}: $${invoice.total}`);
// Trigger your downstream provisioning logic here
break;
default:
console.log(`Unhandled event type ${event.type}`);
}
res.json({ received: true });
});
PHP Example:
<?php
$payload = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_X_PAYENY_SIGNATURE'] ?? '';
$secret = getenv('PAYENY_WEBHOOK_SECRET');
$computed = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($computed, $sigHeader)) {
http_response_code(401);
die('Invalid signature');
}
$event = json_decode($payload, true);
if ($event['type'] === 'invoice.paid') {
// Process successful invoice payment
}
http_response_code(200);
echo json_encode(['received' => true]);
5. Local Webhook Testing & Best Practices
- Always return 200 OK immediately: Do not perform long-running background tasks inside the synchronous webhook handler; push jobs to a queue (Redis / RabbitMQ).
- Implement idempotency: Store processed
event.ids to prevent duplicate action if an event is redelivered. - Use test mode keys: Develop with test API tokens and simulated events in the Payeny Developer Dashboard before going live.