Webhooks Overview

Webhooks deliver real-time HTTP POST notifications to your server when events occur on your orders. This eliminates the need to poll the API for status updates.

Supported Events

EventEmitted when
order.completedPayment completes successfully at order creation time
order.fulfilledAll products are issued and their documents are ready to download
order.failedAn order transitions to REJECTED or FAILED
order.refundedAn order transitions to REFUND_COMPLETED
order.cancelledAn unpaid order transitions to EXPIRED
testYou call POST /webhooks/:id/test
*Wildcard subscription; receive all of the above

order.completed and order.fulfilled are two different milestones. order.completed means the payment was accepted; nothing is issued yet. order.fulfilled means every line item is issued and its documents are stored, so it is the event to act on when you serve your own customers (for example when Vignetim customer emails are suppressed for your organization).

Asynchronous events (order.fulfilled, order.failed, order.refunded, order.cancelled) are delivered from the order pipeline and are deduplicated: at most one delivery per order and event type within 24 hours, even for multi-product orders. Sandbox orders only ever produce order.completed (with sandbox: true) and test events.

Webhook Envelope

Every delivery has the same envelope:

json
{
	"event": "order.completed",
	"data": { "...": "event-specific payload" },
	"timestamp": "2026-03-20T14:31:15.000Z",
	"webhookId": "1f2e3d4c-5b6a-7980-cdef-0123456789ab"
}

Event Payloads

order.completed

Emitted at payment time. For live orders:

json
{
	"event": "order.completed",
	"data": {
		"transactionId": "b6f0e2d4-8c1a-4e5f-9b3d-7a2c4e6f8a0b",
		"status": "COMPLETED",
		"externalReference": "YOUR-ORDER-REF-001"
	},
	"timestamp": "2026-03-20T14:31:15.000Z",
	"webhookId": "1f2e3d4c-5b6a-7980-cdef-0123456789ab"
}

Sandbox orders additionally carry orderId and sandbox: true:

json
{
	"event": "order.completed",
	"data": {
		"orderId": "0d9c1c7e-3f4b-4d2a-9b1e-2f6a8c5d7e90",
		"transactionId": "sandbox_a1b2c3d4e5f67890",
		"status": "COMPLETED",
		"sandbox": true,
		"externalReference": "YOUR-ORDER-REF-001"
	},
	"timestamp": "2026-03-20T14:31:15.000Z",
	"webhookId": "1f2e3d4c-5b6a-7980-cdef-0123456789ab"
}

data.orderId is currently present on sandbox order.completed events only. On live events, correlate by externalReference (recommended) or transactionId.

order.fulfilled

Emitted once every line item of the order is COMPLETED and its documents are stored:

json
{
	"event": "order.fulfilled",
	"data": {
		"orderId": "0d9c1c7e-3f4b-4d2a-9b1e-2f6a8c5d7e90",
		"status": 4,
		"statusLabel": "COMPLETED",
		"externalReference": "YOUR-ORDER-REF-001",
		"products": [
			{
				"productId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
				"productTypeId": 1,
				"status": 4,
				"statusLabel": "COMPLETED"
			}
		],
		"documents": [
			{
				"type": "CONFIRMATION_PDF",
				"typeId": 1,
				"orderProductId": "7e6d5c4b-3a2f-1e0d-9c8b-7a6f5e4d3c2b"
			}
		],
		"documentsUrl": "https://api.vignetim.com/v2/partners/orders/0d9c1c7e-3f4b-4d2a-9b1e-2f6a8c5d7e90/documents",
		"timestamp": "2026-03-21T09:12:00.000Z"
	},
	"timestamp": "2026-03-21T09:12:00.000Z",
	"webhookId": "1f2e3d4c-5b6a-7980-cdef-0123456789ab"
}

documents carries identifiers only. Download URLs are short-lived, so fetch them on demand from Get Order Documents (documentsUrl) when you are ready to deliver the files to your customer.

order.failed, order.refunded, order.cancelled

Delivered from the async status bridge:

json
{
	"event": "order.failed",
	"data": {
		"orderId": "0d9c1c7e-3f4b-4d2a-9b1e-2f6a8c5d7e90",
		"status": "REJECTED",
		"externalReference": "YOUR-ORDER-REF-001",
		"timestamp": "2026-03-21T09:12:00.000Z"
	},
	"timestamp": "2026-03-21T09:12:00.000Z",
	"webhookId": "1f2e3d4c-5b6a-7980-cdef-0123456789ab"
}

data.status is the raw order status name that triggered the event (REJECTED or FAILED for order.failed, REFUND_COMPLETED for order.refunded, EXPIRED for order.cancelled). externalReference is omitted if the order was created without one.

test

json
{
	"event": "test",
	"data": {
		"message": "This is a test webhook delivery from Vignetim"
	},
	"timestamp": "2026-03-20T14:31:15.000Z",
	"webhookId": "1f2e3d4c-5b6a-7980-cdef-0123456789ab"
}

Delivery Headers

Each webhook delivery includes the following headers:

HeaderDescription
X-Webhook-SignatureHMAC-SHA256 hex signature of the request body, signed with the webhook's signingSecret
X-Webhook-EventThe event type (e.g., order.completed)
X-Webhook-TimestampISO 8601 timestamp of when the event was generated
Content-Typeapplication/json

Signature Verification

Always verify the webhook signature to ensure the payload is authentic and has not been tampered with.

javascript
import crypto from 'crypto';

function verifyWebhookSignature(body, signature, secret) {
	const expected = crypto.createHmac('sha256', secret).update(body).digest('hex');

	return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

// In your webhook handler:
app.post('/webhooks/vignetim', (req, res) => {
	const signature = req.headers['x-webhook-signature'];
	const rawBody = req.rawBody; // Ensure you capture the raw body

	if (!verifyWebhookSignature(rawBody, signature, 'your-webhook-signing-secret')) {
		return res.status(401).send('Invalid signature');
	}

	const event = req.body;
	console.log(`Received event: ${event.event}`);

	// Process the event...

	res.status(200).send('OK');
});

The signingSecret is returned exactly once, in the POST /webhooks creation response.

Retry Policy

Each event is attempted up to 3 times, with a 10-second timeout per attempt:

AttemptDelay before attempt
1st attemptImmediate
2nd attempt1 second
3rd attempt5 seconds

A delivery succeeds on any 2xx response. After all 3 attempts fail, the delivery is marked as failed and the webhook's failureCount is incremented; a successful delivery resets it to 0.

Auto-Disable

If a webhook endpoint accumulates 10 consecutive delivery failures, it is automatically disabled (active: false). Re-enable it with PUT /webhooks/:id and { "active": true } once your endpoint is healthy. Use POST /webhooks/:id/test to verify connectivity first.