Search docs...

Search docs...

Search docs...

Using webhooks

Using webhooks

Using webhooks

Using webhooks

Webhooks notify your application in real-time when certificate processing completes. This is the recommended approach for production integrations—no polling required.

Overview

Instead of repeatedly calling the API to check certificate status, register a webhook endpoint to receive events automatically:

Sequence diagram of automated COI review process
1099Policy automated COI webhook sequence diagram

Available events

EventTriggered when
certificate.approvedAll requirements passed
certificate.flaggedOne or more requirements failed, needs manual review
certificate.deniedCertificate was manually denied by a compliance reviewer
certificate.errorTriggered when an error occurs during certificate processing

Registering a webhook endpoint

Register your endpoint via the API:

bash
curl https://api.1099policy.com/api/v1/webhook_endpoints \
  -u YOUR_SECRET_KEY: \
  -d url="https://your-app.com/webhooks/1099policy" \
  -d "events[]=certificate.approved" \
  -d "events[]=certificate.flagged" \
  -d "events[]=certificate.denied"

Response:

json
{
  "id": "we_abc123",
  "url": "https://your-app.com/webhooks/1099policy",
  "events": [
    "certificate.approved",
    "certificate.flagged",
    "certificate.denied"
  ],
  "secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxx",
  "created": 1706745600
}
Webhook secret key

Save the secret value. You'll need it to verify webhook signatures. The secret is also available in your Dashboard under the Webhooks tab.

Event payload

When an event fires, we send a POST request to your endpoint:

json
{
  "id": "evt_abc123",
  "type": "certificate.flagged",
  "created": 1706745620,
  "data": {
    "object": {
      "id": "cert_aBcDeFgHiJ",
      "contractor": "cn_abc123",
      "status": "flagged",
      "created": 1706745600
    }
  }
}

Payload fields

FieldDescription
idUnique event identifier
typeEvent type (e.g., certificate.approved)
createdUnix timestamp of when the event was created
data.objectThe certificate object that triggered the event

The data.object contains basic certificate information. To get full details including review results, make a follow-up API call:

bash
curl "https://api.1099policy.com/api/v1/files/certificates/cert_aBcDeFgHiJ?expand[]=review_results.full" \
  -u YOUR_SECRET_KEY:

Handling webhooks

Here's a complete example of a webhook handler:

python
from flask import Flask, request, jsonify
import hmac
import hashlib
import requests

app = Flask(__name__)

WEBHOOK_SECRET = 'whsec_xxxxxxxxxxxxxxxxxxxxxxxx'
API_SECRET_KEY = 'YOUR_SECRET_KEY'

@app.route('/webhooks/1099policy', methods=['POST'])
def handle_webhook():
    # 1. Verify signature
    payload = request.get_data(as_text=True)
    signature = request.headers.get('x-convoy-signature')
    timestamp = request.headers.get('convoy-timestamp')
    
    if not verify_signature(payload, timestamp, signature, WEBHOOK_SECRET):
        return jsonify({'error': 'Invalid signature'}), 401
    
    # 2. Parse event
    event = request.json
    event_type = event['type']
    certificate = event['data']['object']
    
    # 3. Handle by event type
    if event_type == 'certificate.approved':
        handle_approved(certificate)
    
    elif event_type == 'certificate.flagged':
        handle_flagged(certificate)
    
    elif event_type == 'certificate.denied':
        handle_denied(certificate)
    
    # 4. Acknowledge receipt
    return jsonify({'received': True}), 200


def verify_signature(payload, timestamp, signature, secret):
    request_data = f'{timestamp},{payload}'
    expected = hmac.new(
        secret.encode(),
        request_data.encode(),
        hashlib.sha512
    ).hexdigest()
    return hmac.compare_digest(expected, signature)


def handle_approved(certificate):
    contractor_id = certificate['contractor']
    # Contractor is cleared - proceed with onboarding
    activate_contractor(contractor_id)


def handle_flagged(certificate):
    # Fetch full results to see what failed
    response = requests.get(
        f"https://api.1099policy.com/api/v1/files/certificates/{certificate['id']}?expand[]=review_results.full",
        auth=(API_SECRET_KEY, '')
    )
    full_certificate = response.json()
    
    # Queue for compliance team review
    queue_for_review(full_certificate)


def handle_denied(certificate):
    contractor_id = certificate['contractor']
    # Notify contractor they need new coverage
    send_denial_notification(contractor_id)

Verifying signatures

Always verify webhook signatures to ensure requests are from 1099Policy, not a malicious actor.

The signature is sent in the x-convoy-signature header. To verify:

  1. Get the convoy-timestamp header value
  2. Concatenate the timestamp and request body with a comma: {timestamp},{payload}
  3. Compute the HMAC-SHA512 hex digest using your webhook secret
  4. Compare with the x-convoy-signature header value

Python

python
import hmac
import hashlib

def verify_signature(payload, timestamp, signature, secret):
    request_data = f'{timestamp},{payload}'
    expected = hmac.new(
        secret.encode(),
        request_data.encode(),
        hashlib.sha512
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

# Usage
payload = request.get_data(as_text=True)
timestamp = request.headers.get('convoy-timestamp')
signature = request.headers.get('x-convoy-signature')

if verify_signature(payload, timestamp, signature, WEBHOOK_SECRET):
    # Valid request from 1099Policy
    process_event(request.json)

Node.js

javascript
const crypto = require('crypto');

function verifySignature(payload, timestamp, signature, secret) {
  const requestData = `${timestamp},${payload}`;
  const expected = crypto
    .createHmac('sha512', secret)
    .update(requestData)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

// Usage
const payload = req.body; // raw body string
const timestamp = req.headers['convoy-timestamp'];
const signature = req.headers['x-convoy-signature'];

if (verifySignature(payload, timestamp, signature, WEBHOOK_SECRET)) {
  // Valid request from 1099Policy
  processEvent(JSON.parse(payload));
}
Webhook secret key

Use constant-time comparison functions (hmac.compare_digest in Python, crypto.timingSafeEqual in Node.js) to prevent timing attacks.

Responding to webhooks

Your endpoint should:

  1. Return 2xx quickly — Respond within 30 seconds to acknowledge receipt
  2. Process asynchronously — Queue heavy work (database updates, notifications) for background processing
  3. Be idempotent — The same event may be delivered more than once; handle duplicates gracefully

Example: Async processing with a task queue

python
@app.route('/webhooks/1099policy', methods=['POST'])
def handle_webhook():
    # Verify signature...
    
    event = request.json
    
    # Queue for async processing
    task_queue.enqueue(process_certificate_event, event)
    
    # Return immediately
    return jsonify({'received': True}), 200

Retry behavior

If your endpoint returns a non-2xx status code or times out, we retry delivery with exponential backoff. After multiple failed attempts, the webhook is marked as failed.

To avoid missed events:

  • Return 2xx even if you encounter an error processing the event (log the error and handle it separately)
  • Implement a fallback that periodically checks for certificates in pending or processing status that may have been missed

Managing webhook endpoints

List all endpoints

bash
curl https://api.1099policy.com/api/v1/webhook_endpoints \
  -u YOUR_SECRET_KEY:

Update an endpoint

bash
curl -X PUT https://api.1099policy.com/api/v1/webhook_endpoints/we_abc123 \
  -u YOUR_SECRET_KEY: \
  -d url="https://your-app.com/webhooks/new-path" \
  -d "events[]=certificate.approved" \
  -d "events[]=certificate.flagged"

Delete an endpoint:

bash
curl -X DELETE https://api.1099policy.com/api/v1/webhook_endpoints/we_abc123 \
  -u YOUR_SECRET_KEY:

Testing webhooks locally

During development, use a tunneling service to expose your local server:

  1. Start your local server (e.g., on port 5000)
  2. Use a tool like ngrok to create a public URL:
    bash
    ngrok http 5000
    
  3. Register the ngrok URL as your webhook endpoint:
    bash
    curl https://api.1099policy.com/api/v1/webhook_endpoints \
      -u YOUR_SECRET_KEY: \
      -d url="https://abc123.ngrok.io/webhooks/1099policy" \
      -d "events[]=certificate.approved" \
      -d "events[]=certificate.flagged" \
      -d "events[]=certificate.denied"
    
  4. Upload a test certificate and watch for the webhook

Next: Learn how to handle edge cases and troubleshooting scenarios.

Was this page helpful?

Yes

No

Was this page helpful?

Yes

No

Was this page helpful?

Yes

No

Was this page helpful?

Yes

No

Was this page helpful?

Yes

No

Was this page helpful?

Yes

No

Working with review results

Troubleshooting

© Copyright 2024. All rights reserved.

© Copyright 2024. All rights reserved.

© Copyright 2024. All rights reserved.

© Copyright 2024. All rights reserved.

© Copyright 2024. All rights reserved.

© Copyright 2024. All rights reserved.