Webhooks

Receive import data directly on your server in real-time.

Webhooks allow you to receive import data on your backend as soon as an import completes. This is useful for processing data server-side, syncing with databases, or triggering automated workflows.

Configuration

Configure your webhook URL in the dashboard under Settings → Webhooks.

Webhook Settings
https://your-app.com/api/webhooks/rowporter
whsec_aBcDeFgHiJkLmNoPqRsTuVwXyZ

Used to verify webhook signatures

Payload Format

When an import completes, we send a POST request to your webhook URL with the following payload:

POST /api/webhooks/rowporter
{
  "event": "import.completed",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "importId": "imp_abc123xyz",
  "templateId": "tpl_def456uvw",
  "organizationId": "org_ghi789rst",
  
  "file": {
    "name": "contacts.csv",
    "type": "csv",
    "size": 15234
  },
  
  "stats": {
    "totalRows": 150,
    "validRows": 145,
    "skippedRows": 5,
    "errorRows": 5
  },
  
  "metadata": {
    "source": "onboarding",
    "campaign": "q1-2024",
    "userId": "user-123"
  },
  
  "user": {
    "id": "user-123",
    "email": "user@example.com",
    "name": "John Doe"
  },
  
  "columnMapping": {
    "Email Address": "email",
    "Full Name": "name",
    "Phone Number": "phone"
  },
  
  "data": {
    "columns": ["email", "name", "phone"],
    "rows": [
      {
        "email": "john@example.com",
        "name": "John Doe",
        "phone": "+1-555-0123"
      },
      {
        "email": "jane@example.com",
        "name": "Jane Smith",
        "phone": "+1-555-0456"
      },
      // ... more rows
    ]
  }
}

Event Types

EventDescription
import.completedImport finished successfully
import.failedImport failed due to an error

Verifying Webhooks

To ensure webhooks are coming from Rowporter, verify the signature using your webhook secret.

Signature Header

Every webhook request includes an X-Rowporter-Signature header containing an HMAC-SHA256 signature of the request body.

X-Rowporter-Signature: sha256=abc123def456...

Verification Example (Node.js)

import crypto from 'crypto';

function verifyWebhook(payload, signature, secret) {
  const expectedSignature = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Express.js example
app.post('/api/webhooks/rowporter', express.raw({type: 'application/json'}), (req, res) => {
  const signature = req.headers['x-rowporter-signature'];
  const payload = req.body.toString();
  
  if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  const event = JSON.parse(payload);
  
  // Process the webhook
  if (event.event === 'import.completed') {
    console.log('Import completed:', event.importId);
    console.log('Rows:', event.data.rows.length);
    
    // Save to your database
    await saveContacts(event.data.rows);
  }
  
  res.status(200).json({ received: true });
});

Verification Example (Python)

import hmac
import hashlib

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    expected = 'sha256=' + hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

# Flask example
@app.route('/api/webhooks/rowporter', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Rowporter-Signature')
    payload = request.get_data()
    
    if not verify_webhook(payload, signature, os.environ['WEBHOOK_SECRET']):
        return jsonify({'error': 'Invalid signature'}), 401
    
    event = request.get_json()
    
    if event['event'] == 'import.completed':
        print(f"Import completed: {event['importId']}")
        save_contacts(event['data']['rows'])
    
    return jsonify({'received': True}), 200

Retry Policy

If your webhook endpoint returns a non-2xx status code or times out, we'll automatically retry.

Retry attemptsUp to 5 retries
Retry schedule1 min, 5 min, 30 min, 2 hours, 24 hours
Timeout30 seconds
Success responseAny 2xx status code

Best Practices

Return 200 quickly

Acknowledge the webhook immediately, then process data asynchronously. Don't make the webhook wait for your database operations.

Handle duplicates

Use the importId to deduplicate. Webhooks may be delivered more than once in rare cases.

Verify signatures

Always verify the webhook signature to ensure requests are authentic.

Log webhook events

Keep logs of received webhooks for debugging and audit purposes.

Large imports

For imports with more than 10,000 rows, the data.rows array may be paginated. Check the pagination field and use the API to fetch remaining data if needed.

Testing Webhooks

You can test your webhook endpoint using tools like:

ngrok

Expose your local server to the internet for testing.

ngrok http 3000
webhook.site

Free tool to inspect webhook payloads.

webhook.site →

Test with cURL

curl -X POST https://your-app.com/api/webhooks/rowporter \
  -H "Content-Type: application/json" \
  -H "X-Rowporter-Signature: sha256=test" \
  -d '{
    "event": "import.completed",
    "importId": "imp_test123",
    "templateId": "tpl_test456",
    "timestamp": "2024-01-15T10:30:00Z",
    "data": {
      "columns": ["email", "name"],
      "rows": [
        {"email": "test@example.com", "name": "Test User"}
      ]
    }
  }'

Troubleshooting

Webhook not received

  • Check that your webhook URL is correct in the dashboard
  • Ensure your server is publicly accessible
  • Check firewall rules allow incoming requests
  • Review webhook delivery logs in the dashboard

Signature verification failing

  • Ensure you're using the raw request body, not parsed JSON
  • Check that your webhook secret matches exactly
  • Make sure you're comparing the full signature including "sha256=" prefix

Timeout errors

  • Return 200 immediately, process data asynchronously
  • Use a message queue for heavy processing
  • Check your server logs for errors