Webhooks
Webhooks allow you to receive real-time notifications when events occur in your WalletWerk account. This enables you to keep your systems in sync without polling the API.
Setting Up Webhooks
Create a Webhook Endpoint
- Log in to your dashboard
- Navigate to Developer → Webhooks
- Click Add Webhook
- Enter your endpoint URL (must be HTTPS in production)
- Select which events to subscribe to
- Copy and securely store your signing secret You'll only see it once!
Verify Webhook Signatures
All webhook payloads include a signature in the
X-WalletWerk-Signatureheader. You must verify this signature to ensure the request is from WalletWerk.How it works:
- WalletWerk sends the raw JSON body (as a string) and a signature header
- You compute an HMAC-SHA256 hash of the raw JSON body using your secret
- Compare your computed hash with the signature in the header
- If they match, the webhook is authentic
Important: You must use the raw JSON body (as received, before parsing), not the parsed JSON object. The signature is computed from the exact bytes of the request body.
Common Mistakes:
- ❌ Parsing JSON before verifying the signature
- ❌ Using
req.bodyafter JSON parsing (Express) orrequest.json(Flask) instead of raw body - ❌ Not using constant-time comparison (vulnerable to timing attacks)
- ✅ Always verify the signature using the raw request body string
When We Send Events
WalletWerk sends webhook events when:
- A wallet card is created, updated, or changes status
- A recipient is added, invited, or updated
- A campaign is created or updated
- A user adds or removes a card from their wallet
- A card is revoked or expires
Event Types
Card Events
Response Fields
| Event | Description | When It's Triggered |
|---|---|---|
card.created | Wallet card created | When a card is successfully generated for a recipient |
card.active | Card added to wallet | When a user adds the card to their Apple/Google wallet |
card.updated | Card data updated | When card data changes (propagated to wallet) |
card.removed | Card removed from wallet | When a user deletes the card from their wallet |
card.revoked | Card revoked | When you revoke a card via API or dashboard |
card.expired | Card expired | When a card passes its expiration date |
Recipient Events
Response Fields
| Event | Description | When It's Triggered |
|---|---|---|
recipient.created | Recipient added | When a recipient is added to a campaign |
recipient.invited | Invite sent | When an invite is sent to a recipient |
recipient.updated | Recipient updated | When recipient data is modified |
Campaign Events
Response Fields
| Event | Description | When It's Triggered |
|---|---|---|
campaign.created | Campaign created | When a new campaign is created |
campaign.updated | Campaign updated | When campaign data is modified |
campaign.completed | Campaign completed | When a campaign ends or is marked as completed |
Event Payload
All webhook payloads follow this structure:
{
"event": "card.active",
"timestamp": "2025-12-09T10:30:00Z",
"organization_id": 123,
"data": {
"card_id": "card_abc123",
"recipient_id": "recipient_xyz789",
"campaign_id": "campaign_def456",
"provider": "apple",
"old_status": "invited",
"new_status": "active",
"metadata": {}
}
}
Status Fields in Webhook Payloads:
- Card events (
card.*):old_statusandnew_statusshow wallet card status changes (active,removed,revoked,expired) - Recipient events (
recipient.*):old_statusandnew_statusshow recipient status changes (pending,created,invited,active,revoked,deleted) - Campaign events (
campaign.*): Status fields may not be present
Verify Signatures
Every webhook request includes these headers:
Headers
| Header | Description |
|---|---|
X-WalletWerk-Signature | HMAC-SHA256 signature of the payload |
X-WalletWerk-Event | The event type (e.g., card.active) |
X-WalletWerk-Timestamp | ISO 8601 timestamp of when the event occurred |
X-WalletWerk-Delivery-ID | Unique ID for this webhook delivery (for idempotency) |
Content-Type | Always application/json |
See the Setting Up Webhooks section above for signature verification code examples.
Retry Behavior and Disabling
Retry Logic
WalletWerk will retry failed deliveries with exponential backoff:
- First retry: 1 minute
- Second retry: 5 minutes
- Third retry: 15 minutes
- Fourth retry: 1 hour
- Maximum: 24 hours
Timeout
Webhook endpoints must respond within 30 seconds. If your endpoint doesn't respond within this time, WalletWerk will consider the delivery failed and retry.
Failure Handling
Webhooks are automatically disabled after 10 consecutive failures. You'll need to re-enable them from the dashboard.
Idempotency
Use the X-WalletWerk-Delivery-ID header to ensure idempotent processing. Each webhook delivery has a unique ID that you can use to prevent duplicate processing:
// Store processed delivery IDs
const processedIds = new Set();
app.post('/webhooks/walletwerk', express.raw({ type: 'application/json' }), (req, res) => {
const deliveryId = req.headers['x-walletwerk-delivery-id'];
// Check if we've already processed this delivery
if (processedIds.has(deliveryId)) {
return res.status(200).send('OK'); // Already processed
}
// Process the event...
processedIds.add(deliveryId);
res.status(200).send('OK');
});
Testing Webhooks
You can test your webhook endpoint from the dashboard:
- Navigate to Developer → Webhooks
- Click the test button (send icon) next to your webhook
- A test payload will be sent to your endpoint with event type
webhook.test
The test payload follows the same format as regular webhook events, allowing you to verify your signature verification and event handling logic.