Integrating Custom Payment Processors
So you've got a payment provider that isn't on our official integrations list. Maybe it's a regional processor your customers prefer, or maybe you've negotiated rates that are just too good to walk away from. Either way, you're in the right place. This guide covers how to bridge FieldPulse with pretty much any payment provider that exposes a decent API.
Yeah, this one tripped me up too when I first looked at it. The webhook architecture wasn't quite what I expected, and I spent a solid afternoon wondering why my capture events weren't firing. Turns out I had the event subscription wrong. More on that below.
What You'll Need
- A payment processor with webhook or API notification support (Stripe, Square, Adyen, Braintree, etc.)
- A lightweight middleware service (AWS Lambda, Cloudflare Workers, your own server—whatever you're comfortable with)
- Your FieldPulse API credentials (see Using the FieldPulse API if you haven't set this up yet)
- Patience for some trial and error on the webhook verification
The Basic Architecture
Here's how I typically set this up:
Payment Processor → Your Middleware → FieldPulse API
(webhook) (REST calls)
The payment processor sends webhooks to your middleware when things happen—payment authorized, captured, failed, refunded. Your middleware translates those events into FieldPulse operations, usually updating work order status or recording payment against an invoice.
Step 1: Configure Your Webhook Endpoint
Most processors let you configure webhook URLs in their dashboard. Point this at your middleware. For Stripe, for example:
POST https://your-middleware.example.com/webhook/stripe
Content-Type: application/json
Stripe-Signature: t=1492774577,v1=5257a869... # verify this!
{
"id": "evt_1234567890",
"type": "invoice.payment_succeeded",
"data": {
"object": {
"id": "in_9876543210",
"amount_paid": 15000,
"customer": "cus_abcdefghij"
}
}
}
Don't skip signature verification. Seriously. I've seen people proxy webhooks straight through without checking, and it's... not great when someone figures out they can POST random payment events to your endpoint. Most SDKs make this easy:
// Stripe Node.js example
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const event = stripe.webhooks.constructEvent(
payload,
sigHeader,
webhookSecret
);
Step 2: Map Payment Events to FieldPulse Operations
Once you've verified the webhook, you need to figure out what to do in FieldPulse. Common patterns:
| Payment Event | FieldPulse Action |
|---|---|
| payment_intent.succeeded | Update work order to "Paid", record payment amount |
| charge.refunded | Update work order to "Refunded", log reason |
| invoice.payment_failed | Add note to work order, flag for follow-up |
The trick is correlating the payment with the right FieldPulse record. I usually store the FieldPulse work order ID in the payment processor's metadata when creating the intent:
// When creating the payment intent from your app
const paymentIntent = await stripe.paymentIntents.create({
amount: 15000,
currency: 'usd',
metadata: {
fieldpulse_work_order_id: 'WO-2024-001234'
}
});
Then in your webhook handler, you just pull that back out:
const workOrderId = event.data.object.metadata.fieldpulse_work_order_id;
Step 3: Update FieldPulse via API
Now the actual FieldPulse call. You'll need to:
- Authenticate with your API key (see API authentication docs)
- Find the work order (or use the ID you stored)
- Update status and/or add a payment record
Here's where I got stuck initially. I was trying to POST to a /payments endpoint that doesn't exist. The correct approach is updating the work order with payment information in the billing object:
// PATCH /api/v2/work-orders/{id}
const response = await fetch(
`https://api.fieldpulse.com/v2/work-orders/${workOrderId}`,
{
method: 'PATCH',
headers: {
'Authorization': `Bearer ${FP_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
status: 'completed_and_paid',
billing: {
payment_status: 'paid',
payment_amount: event.data.object.amount_received / 100, // cents to dollars
payment_method: 'credit_card',
external_reference: event.data.object.id
},
notes: [
{
content: `Payment captured via Stripe: ${event.data.object.id}`,
visibility: 'internal'
}
]
})
}
);
Note the external_reference field—super useful for reconciliation later. Your accounting team will thank you.
Edge Cases That'll Get You
Okay, so here are the things that actually broke in production for me:
Duplicate webhooks: Payment processors often retry webhooks if they don't get a 200 response quickly enough. Your middleware needs to be idempotent—same event processed twice shouldn't create two payment records. I usually check if I've already seen this event ID before processing.
// Simple idempotency check
const seen = await redis.get(`stripe:event:${event.id}`);
if (seen) {
console.log(`Already processed ${event.id}, skipping`);
return 200;
}
await redis.setex(`stripe:event:${event.id}`, 86400, '1');
Partial captures: Some processors support authorizing $500 but only capturing $400. Make sure your FieldPulse update handles the actual captured amount, not the authorized amount.
Currency conversion: If you're dealing with international customers, the payment amount in the webhook might be in a different currency than your FieldPulse billing. You'll need to convert or store both amounts clearly.
Network timeouts: FieldPulse's API has rate limits (see rate limit docs). If you're processing a batch of webhooks, you might hit them. I implement exponential backoff—wait and retry on 429 responses.
Testing Your Integration
Don't test this with real money. Most processors have test/sandbox environments with specific card numbers that trigger different behaviors:
- Stripe:
4242 4242 4242 4242(always succeeds) - Stripe:
4000 0000 0000 0002(generic decline) - Most processors have similar test PANs
Also worth checking out ngrok or similar for local webhook testing—you'll need a public HTTPS URL that tunnels to your local dev environment.
Security Considerations
I should mention this even though it's boring: store your API keys properly. Not in your repo. Use environment variables or a secrets manager. Rotate them occasionally. The usual drill.
Also, verify those webhook signatures. I know I said it already, but people still get this wrong.
See Also
- Using the FieldPulse API — authentication and endpoints
- Setting Up Webhooks — FieldPulse's outbound webhooks (different direction, same concepts)
- API Rate Limits and Best Practices — don't get throttled
- Connecting FieldPulse to QuickBooks — if you need payment data in accounting too
Got a specific processor you're trying to integrate? Hit me up in the comments or check the developer forum. I've probably stubbed my toe on the same corner you just found.