Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.uip.digital/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Webhooks let your application receive real-time notifications for specific UIP events. UIP sends a POST request to your configured webhook URL with a cryptographically signed JSON payload.
Webhooks are only used for:
  • Message API — when a user signs or declines a signature-required message
  • Webhook testing — when you call the test endpoint
Identify and Sign APIs do NOT use webhooks. Use polling with GET /v1/identify/:id or GET /v1/sign/:id instead.

Setting up webhooks

1. Configure the webhook URL

  1. Log into the Business Dashboard
  2. Open Settings > Webhooks
  3. Enter your webhook endpoint URL (HTTPS in production)
  4. Save
HTTPS required. Production webhook URLs must use HTTPS.

2. Retrieve the webhook secret

Your webhook secret is used to verify that requests came from UIP and weren’t tampered with in transit.
  1. In the dashboard, go to Settings > Webhooks
  2. Copy your Webhook Secret (shown after saving your webhook URL)
  3. Store it in your application’s environment variables
You can rotate the secret at any time from the dashboard. All subsequent webhook deliveries will use the new secret.
Keep the secret secure. Never commit it to version control or expose it in client-side code.

Verifying webhook signatures

Every webhook request includes an X-UIP-Signature header containing an HMAC SHA-256 signature. You must verify this signature to ensure requests are from UIP.

Algorithm

  1. Get the X-UIP-Signature header from the request
  2. Calculate HMAC SHA-256 of the raw request body using your webhook secret
  3. Compare the calculated signature with the header value
  4. Reject the request if signatures don’t match

Implementation examples

const crypto = require('crypto');

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-uip-signature'];
  const webhookSecret = process.env.UIP_WEBHOOK_SECRET;

  const hmac = crypto.createHmac('sha256', webhookSecret);
  hmac.update(JSON.stringify(req.body));
  const expectedSignature = hmac.digest('hex');

  if (signature !== expectedSignature) {
    return res.status(401).send('Invalid signature');
  }

  const { event, data } = req.body;

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

Webhook events

Event: message

Sent when a user signs or declines a signature-required message via the Message API.
{
  "event": "message",
  "data": {
    "signing_uip_id": "user_abc123def456",
    "message_id": "msg_xyz789abc123",
    "audit_id": "audit_9z8y7x6w5v4u"
  }
}
Fields:
  • signing_uip_id — UIP ID of the user who signed or declined
  • message_id — Message ID from your original request
  • audit_id — Permanent audit trail reference (save for compliance)
Determining action: The webhook structure is identical for both signed and declined messages. Query the Audit API using the audit_id to determine the outcome.

Event: test

Sent when you test your webhook configuration via the Test Webhook endpoint.
{
  "event": "test",
  "timestamp": "2025-01-11T12:30:00Z",
  "test": true
}
Purpose: Verify your webhook endpoint is reachable and correctly validates signatures.

When webhooks are sent

Webhook sent when: User signs or declines a signature-required message (signature_required: true)No webhook sent when:
  • Message does not require signature (signature_required: false)
  • User doesn’t respond and the signature window expires
  • Message is invalidated before the user responds
Webhook sent when: You call the Test Webhook endpointPurpose: Verify your webhook configuration before production use

Best practices

Always verify signatures

Validate the X-UIP-Signature header on every request to prevent spoofing and tampering

Return 200 quickly

Process webhooks asynchronously and return 200 immediately to avoid timeouts

Store audit IDs

Save audit_id from every webhook for permanent proof, compliance, and legal verification

Handle idempotency

UIP may send the same webhook multiple times. Use audit_id or message_id to detect duplicates

Use HTTPS

Production webhook URLs must use HTTPS. HTTP is not allowed for security reasons.

Test before production

Use the Test Webhook endpoint during development to verify your setup works correctly

Error handling

If your webhook endpoint returns an error (non-2xx status code) or times out, UIP will retry the webhook with exponential backoff. Retry schedule: UIP retries webhook delivery once after a short delay. If both attempts fail, the webhook is marked as failed.
  • Attempt 1: Immediate
  • Attempt 2: After a short delay
Handle retries. Design your webhook handler to be idempotent. Use audit_id or message_id to detect duplicate deliveries.

Webhook handler template

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

app.post('/webhook', async (req, res) => {
  // 1. Verify signature
  const signature = req.headers['x-uip-signature'];
  const webhookSecret = process.env.UIP_WEBHOOK_SECRET;

  const hmac = crypto.createHmac('sha256', webhookSecret);
  hmac.update(JSON.stringify(req.body));
  const expectedSignature = hmac.digest('hex');

  if (signature !== expectedSignature) {
    return res.status(401).send('Invalid signature');
  }

  // 2. Return 200 immediately
  res.status(200).send('OK');

  // 3. Process webhook asynchronously
  const { event, data } = req.body;

  try {
    switch (event) {
      case 'message':
        await handleMessage(data);
        break;
      case 'test':
        console.log('Test webhook received');
        break;
      default:
        console.warn(`Unknown event type: ${event}`);
    }
  } catch (error) {
    console.error('Webhook processing error:', error);
  }
});

async function handleMessage(data) {
  const { signing_uip_id, message_id, audit_id } = data;

  // Check for duplicate
  const existing = await db.messageSignatures.findOne({ audit_id });
  if (existing) {
    console.log('Duplicate webhook, skipping');
    return;
  }

  // Query audit API to check if signed or declined
  const auditResponse = await fetch(
    `https://api.uip.digital/v1/audit/${audit_id}`,
    {
      headers: { 'Authorization': `Bearer ${process.env.UIP_API_KEY}` }
    }
  );

  const { audit } = await auditResponse.json();
  const wasSigned = audit.signed_accepted;

  // Save to database
  await db.messageSignatures.create({
    signing_uip_id,
    message_id,
    audit_id,
    was_signed: wasSigned,
    received_at: new Date()
  });

  // Handle based on outcome
  if (wasSigned) {
    await handleMessageSigned(message_id);
  } else {
    await handleMessageDeclined(message_id);
  }
}

app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});

Troubleshooting

Problem: API requests fail with request/webhook-unreachable errorCauses:
  • Webhook URL is not publicly accessible
  • Firewall blocking UIP servers
  • Webhook handler not returning 200 status code
  • HTTPS certificate issues
Solutions:
  • Verify webhook URL is publicly accessible
  • Check firewall rules allow incoming requests
  • Ensure webhook returns 200 status for all events
  • Use a valid HTTPS certificate
Problem: Webhook receives requests but signature validation failsCauses:
  • Using wrong webhook secret
  • Calculating signature on a modified body
  • Character encoding issues
Solutions:
  • Copy the webhook secret from the dashboard settings
  • Calculate the signature on the raw request body (before parsing)
  • Use UTF-8 encoding for all strings
Problem: API requests fail with request/webhook-missing errorCauses:
  • No webhook URL configured in the dashboard
  • Webhook URL field is empty
Solutions:
  • Log into the dashboard
  • Navigate to Settings > Webhooks
  • Add your webhook endpoint URL
Problem: Receiving the same webhook multiple timesCauses:
  • UIP retries webhooks if your server returns errors or times out
  • Network issues causing automatic retries
Solutions:
  • Use audit_id or message_id to detect duplicates
  • Make your webhook handler idempotent
  • Store processed webhook IDs in a database to skip duplicates

Test Webhook

Test your webhook configuration

Message API

Messages with webhook signature events

Audit API

Query audit records from webhooks

Error Handling

Handle API and webhook errors