POST/api/v1/webhooks

Create webhook

Registers a new webhook endpoint for the company identified by the X-Company header. The response includes the full signing secret — store it securely immediately, as it will be masked in all subsequent responses.

POST /api/v1/webhooks
⚠️

The secret field is returned in full only on creation. Copy it to a secure location before leaving this page — subsequent GET requests will return a masked value. If you lose the secret, use the regenerate-secret endpoint to issue a new one.

Headers

NameTypeRequiredDescription
AuthorizationstringYesBearer token for authentication
X-CompanystringYesCompany UUID to scope the request
Content-TypestringYesMust be application/json

Request body

NameTypeRequiredDescription
urlstringYesHTTPS destination URL that will receive POST requests
eventsarrayYesArray of event type names to subscribe to (see List event types)
descriptionstringNoHuman-readable label for this webhook endpoint
isActivebooleanNoWhether the webhook is active on creation (default: true)
💡

The url must use HTTPS. HTTP URLs are rejected. Use the wildcard value ["*"] for events to subscribe to all current and future event types.

Request

curl -X POST https://api.storno.ro/api/v1/webhooks \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Company: 550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/webhooks/storno",
    "events": ["invoice.created", "invoice.validated", "invoice.rejected", "invoice.paid"],
    "description": "Production invoice notifications",
    "isActive": true
  }'

Response

Returns the created webhook object with status 201 Created. The secret field contains the full HMAC-SHA256 signing key.

{
  "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "url": "https://your-app.example.com/webhooks/storno",
  "description": "Production invoice notifications",
  "events": [
    "invoice.created",
    "invoice.validated",
    "invoice.rejected",
    "invoice.paid"
  ],
  "isActive": true,
  "secret": "whsec_9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "createdAt": "2026-02-18T10:00:00Z",
  "updatedAt": "2026-02-18T10:00:00Z"
}

Response fields

FieldTypeDescription
uuidstringUnique identifier for the new webhook endpoint
urlstringThe registered destination URL
descriptionstringThe provided description label
eventsarrayList of subscribed event type names
isActivebooleanActive status of the webhook
secretstringFull HMAC-SHA256 signing secret — save this value immediately
createdAtstringISO 8601 creation timestamp
updatedAtstringISO 8601 last-updated timestamp

Verifying webhook signatures

Each delivery includes an X-Storno-Signature header containing a HMAC-SHA256 hex digest of the raw request body signed with the secret. Verify it server-side:

const crypto = require('crypto');

function verifySignature(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

Error codes

CodeDescription
400Validation error — missing required fields or invalid data
401Missing or invalid authentication token
403Insufficient permissions — requires webhook.manage permission
422Business validation error — URL must use HTTPS, or unknown event type specified