Skillshub adobe-webhooks-events
install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/jeremylongshore/claude-code-plugins-plus-skills/adobe-webhooks-events" ~/.claude/skills/comeonoliver-skillshub-adobe-webhooks-events && rm -rf "$T"
manifest:
skills/jeremylongshore/claude-code-plugins-plus-skills/adobe-webhooks-events/SKILL.mdsource content
Adobe Webhooks & Events
Overview
Implement Adobe I/O Events webhook endpoints with proper challenge-response handshake, RSA-SHA256 digital signature verification, and event routing for Creative Cloud Libraries, Experience Platform, and Firefly Services events.
Prerequisites
- Adobe Developer Console project with Events API enabled
- HTTPS endpoint accessible from the internet
installed (optional, for SDK approach)@adobe/aio-lib-events- Understanding of Adobe I/O Events architecture
Instructions
Step 1: Register Webhook via Adobe I/O Events API
// Register a webhook endpoint programmatically import { getAccessToken } from '../adobe/client'; interface EventRegistration { name: string; description: string; webhookUrl: string; eventsOfInterest: Array<{ provider_id: string; // Event provider (e.g., Creative Cloud) event_code: string; // Specific event type }>; deliveryType: 'webhook' | 'webhook_batch'; } export async function registerWebhook(reg: EventRegistration): Promise<any> { const token = await getAccessToken(); const response = await fetch( `https://api.adobe.io/events/${process.env.ADOBE_IMS_ORG_ID}/integrations/${process.env.ADOBE_INTEGRATION_ID}/registrations`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'x-api-key': process.env.ADOBE_CLIENT_ID!, 'Content-Type': 'application/json', }, body: JSON.stringify({ client_id: process.env.ADOBE_CLIENT_ID, name: reg.name, description: reg.description, webhook_url: reg.webhookUrl, events_of_interest: reg.eventsOfInterest, delivery_type: reg.deliveryType || 'webhook', }), } ); if (!response.ok) throw new Error(`Registration failed: ${await response.text()}`); return response.json(); } // Example: Register for Creative Cloud Library events await registerWebhook({ name: 'CC Library Updates', description: 'Track Creative Cloud Library changes', webhookUrl: 'https://api.yourapp.com/webhooks/adobe', eventsOfInterest: [ { provider_id: 'ccstorage', event_code: 'library_create' }, { provider_id: 'ccstorage', event_code: 'library_update' }, { provider_id: 'ccstorage', event_code: 'library_delete' }, ], deliveryType: 'webhook', });
Step 2: Implement Challenge-Response Handshake
When registering a webhook, Adobe sends a
GET request with a challenge query parameter. Your endpoint must respond with the challenge value:
import express from 'express'; const app = express(); app.get('/webhooks/adobe', (req, res) => { // Adobe challenge verification during registration const challenge = req.query.challenge as string; if (challenge) { console.log('Adobe webhook challenge received'); return res.status(200).json({ challenge }); } res.status(400).json({ error: 'Missing challenge parameter' }); });
Step 3: Verify RSA-SHA256 Digital Signatures
Adobe I/O Events uses RSA-SHA256 (not HMAC). Public keys are served from
static.adobeioevents.com:
import crypto from 'crypto'; const publicKeyCache = new Map<string, string>(); async function fetchPublicKey(keyPath: string): Promise<string> { if (publicKeyCache.has(keyPath)) return publicKeyCache.get(keyPath)!; const res = await fetch(`https://static.adobeioevents.com${keyPath}`); if (!res.ok) throw new Error(`Failed to fetch Adobe public key: ${res.status}`); const key = await res.text(); publicKeyCache.set(keyPath, key); return key; } async function verifyAdobeSignature(rawBody: Buffer, headers: Record<string, string>): Promise<boolean> { for (const idx of ['1', '2']) { const sig = headers[`x-adobe-digital-signature-${idx}`]; const keyPath = headers[`x-adobe-public-key${idx}-path`]; if (!sig || !keyPath) continue; try { const publicKey = await fetchPublicKey(keyPath); const verifier = crypto.createVerify('RSA-SHA256'); verifier.update(rawBody); if (verifier.verify(publicKey, sig, 'base64')) return true; } catch (err) { console.warn(`Adobe signature-${idx} verification error:`, err); } } return false; }
Step 4: Event Handler with Routing
// POST handler for incoming events app.post('/webhooks/adobe', express.raw({ type: 'application/json' }), async (req, res) => { // Verify signature if (!await verifyAdobeSignature(req.body, req.headers as any)) { console.error('Invalid Adobe webhook signature'); return res.status(401).json({ error: 'Invalid signature' }); } const event = JSON.parse(req.body.toString()); // Route by event type try { await routeAdobeEvent(event); res.status(200).json({ received: true }); } catch (error: any) { console.error('Event processing failed:', error); res.status(500).json({ error: error.message }); } } ); // Event type definitions type AdobeEventType = | 'library_create' | 'library_update' | 'library_delete' | 'asset_created' | 'asset_updated'; interface AdobeEvent { event_id: string; event: { type: AdobeEventType; activitystreams?: any; xdmEntity?: any; }; recipient_client_id: string; } const eventHandlers: Partial<Record<AdobeEventType, (event: AdobeEvent) => Promise<void>>> = { library_create: async (event) => { console.log('New CC Library created:', event.event_id); // Sync library metadata to your database }, library_update: async (event) => { console.log('CC Library updated:', event.event_id); // Refresh cached library data }, library_delete: async (event) => { console.log('CC Library deleted:', event.event_id); // Remove from local cache/database }, }; async function routeAdobeEvent(event: AdobeEvent): Promise<void> { const handler = eventHandlers[event.event.type]; if (handler) { await handler(event); } else { console.log(`Unhandled Adobe event type: ${event.event.type}`); } }
Step 5: Idempotency (Prevent Duplicate Processing)
import { Redis } from 'ioredis'; const redis = new Redis(process.env.REDIS_URL); async function processEventIdempotently(event: AdobeEvent): Promise<boolean> { const key = `adobe:event:${event.event_id}`; // SET NX with 7-day TTL — returns null if key already exists const result = await redis.set(key, '1', 'EX', 86400 * 7, 'NX'); if (!result) { console.log(`Duplicate Adobe event skipped: ${event.event_id}`); return false; // Already processed } await routeAdobeEvent(event); return true; }
Output
- Webhook registered with Adobe I/O Events
- Challenge-response handshake handler for registration
- RSA-SHA256 signature verification with key caching
- Event routing by type with handler pattern
- Idempotency via Redis to prevent duplicate processing
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Challenge response 400 | Missing JSON content-type | Return as JSON |
| Signature always invalid | Not using raw body | Use before parsing |
| Events not arriving | Registration failed | Check I/O Events dashboard for status |
| Duplicate events | No idempotency | Track in Redis/DB |
| Public key fetch fails | Network/firewall | Whitelist |
Resources
- Adobe I/O Events Webhooks Guide
- I/O Events Registration API
- Signature Verification SDK
- CC Libraries Events
Next Steps
For performance optimization, see
adobe-performance-tuning.