Webhook Recipes
Production-ready integration patterns for connecting Arcform webhook events to popular platforms. Each recipe includes signature verification, error handling, and the complete handler logic.
Arcform → Slack
Forward envelope alerts to a Slack channel in real time
Arcform → Shopify
Verify sender identity before clearing orders for fulfilment
Arcform → Python
Generic Flask handler with signature verification and proof validation
Arcform → XRPL
Anchor envelope hashes on-ledger via Xaman wallet
Prerequisites
Arcform → Slack
Forward sealed envelope alerts to a Slack channel with rich formatting
Forward sealed envelope alerts to a Slack channel with rich formatting
// Arcform → Slack: Forward envelope alerts to a channel
// Requires: express, crypto, @slack/web-api
const express = require('express');
const crypto = require('crypto');
const { WebClient } = require('@slack/web-api');
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.DIDCOMMS_WEBHOOK_SECRET;
const slack = new WebClient(process.env.SLACK_TOKEN);
const CHANNEL = '#arcform-alerts';
function verifySignature(payload, signature) {
const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
hmac.update(JSON.stringify(payload));
return hmac.digest('hex') === signature;
}
app.post('/webhooks/arcform', async (req, res) => {
const sig = req.headers['x-didcomms-signature'];
if (!verifySignature(req.body, sig)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { event, envelope_hash, sender, metadata } = req.body;
await slack.chat.postMessage({
channel: CHANNEL,
text: `:incoming_envelope: *Sealed Envelope Received*
• Hash: \`${envelope_hash}\`
• Sender: \`${sender}\`
• Size: ${metadata.payload_size_bytes} bytes
• Event: ${event}`,
});
res.json({ ok: true });
});
app.listen(3001, () => console.log('Webhook handler on :3001'));Arcform → Shopify
Verify sender identity via proof-of-transit before order fulfilment
Verify sender identity via proof-of-transit before order fulfilment
// Arcform → Shopify: Verify order identity before fulfilment
// Requires: express, crypto, node-fetch
const express = require('express');
const crypto = require('crypto');
const fetch = require('node-fetch');
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.DIDCOMMS_WEBHOOK_SECRET;
const ARCFORM_API = 'https://arcform-api.com';
app.post('/webhooks/arcform-order', async (req, res) => {
const sig = req.headers['x-didcomms-signature'];
const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
hmac.update(JSON.stringify(req.body));
if (hmac.digest('hex') !== sig) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { envelope_hash } = req.body;
// Step 1: Verify the envelope via Arcform
const proof = await fetch(`${ARCFORM_API}/v1/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ envelope_hash }),
}).then(r => r.json());
// Step 2: Check identity verdict
if (proof.identity?.identity_verdict !== 'ok') {
console.error('Identity verification failed:', proof);
// Flag order for manual review in Shopify
return res.json({ action: 'hold', reason: 'identity_unverified' });
}
// Step 3: Proceed with fulfilment
console.log('Identity verified — clearing order for fulfilment');
res.json({ action: 'fulfil', proof_checksum: proof.proof_checksum });
});
app.listen(3002, () => console.log('Shopify handler on :3002'));Arcform → Python (Flask)
Generic webhook handler with signature verification and proof validation
Generic webhook handler with signature verification and proof validation
# Arcform → Python: Generic webhook handler with Flask
# Requires: flask, requests
from flask import Flask, request, jsonify
import hashlib, hmac, os, requests
app = Flask(__name__)
WEBHOOK_SECRET = os.environ['DIDCOMMS_WEBHOOK_SECRET']
ARCFORM_API = 'https://arcform-api.com'
def verify_signature(payload: bytes, signature: str) -> bool:
expected = hmac.new(
WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route('/webhooks/arcform', methods=['POST'])
def handle_webhook():
sig = request.headers.get('X-DIDComms-Signature', '')
if not verify_signature(request.data, sig):
return jsonify({'error': 'Invalid signature'}), 401
data = request.json
envelope_hash = data['envelope_hash']
# Verify integrity via Arcform
proof = requests.post(
f'{ARCFORM_API}/v1/verify',
json={'envelope_hash': envelope_hash},
).json()
if proof.get('status') != 'verified':
return jsonify({'action': 'reject', 'reason': 'unverified'}), 400
# Your business logic here
print(f"Verified envelope {envelope_hash}")
print(f" Identity verdict: {proof['identity']['identity_verdict']}")
print(f" Proof checksum: {proof['proof_checksum']}")
return jsonify({
'action': 'accepted',
'proof_checksum': proof['proof_checksum'],
})
if __name__ == '__main__':
app.run(port=3004, debug=True)Arcform → XRPL Ledger
Anchor envelope hashes on the XRP Ledger as immutable memo transactions
Anchor envelope hashes on the XRP Ledger as immutable memo transactions
// Arcform → Xaman Wallet: Anchor envelope hash on XRPL
// Requires: express, crypto, xrpl
const express = require('express');
const crypto = require('crypto');
const xrpl = require('xrpl');
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.DIDCOMMS_WEBHOOK_SECRET;
app.post('/webhooks/arcform-anchor', async (req, res) => {
const sig = req.headers['x-didcomms-signature'];
const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
hmac.update(JSON.stringify(req.body));
if (hmac.digest('hex') !== sig) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { envelope_hash, metadata } = req.body;
// Connect to XRPL and submit a memo transaction
const client = new xrpl.Client('wss://s1.ripple.com');
await client.connect();
const wallet = xrpl.Wallet.fromSeed(process.env.XRPL_SEED);
const tx = await client.submitAndWait({
TransactionType: 'Payment',
Account: wallet.address,
Destination: wallet.address, // self-payment for memo
Amount: '1', // 1 drop
Memos: [{
Memo: {
MemoType: Buffer.from('arcform/envelope-hash').toString('hex'),
MemoData: Buffer.from(envelope_hash).toString('hex'),
}
}],
}, { wallet });
console.log('Anchored on XRPL:', tx.result.hash);
await client.disconnect();
res.json({ anchored: true, xrpl_hash: tx.result.hash });
});
app.listen(3003, () => console.log('XRPL anchor handler on :3003'));Building Your Own Recipe
Verify the signature
Always validate X-DIDComms-Signature using HMAC-SHA256 with your whsec_ secret before processing any payload.
Extract envelope metadata
The webhook payload contains envelope_hash, sender key, encryption status, and size — never the message content.
Call /v1/verify for proof
For high-assurance flows, verify the envelope hash against the live platform to get the full integrity proof and identity verdict.
Act on the result
Route to Slack, trigger Shopify flows, anchor on-ledger, update a database — the envelope hash is your universal correlation key.