Designing an integration that fails safely
Start with a field map and an owner
Before any code we draw a table: each field in system A, where it lands in system B, what format it takes on the way, and which system is the source of truth. Customer email might belong to the CRM while billing address belongs to the store. Writing this down settles most arguments early, and it exposes the fields with no home, which is where integrations usually stall. The same table becomes the acceptance test.
Polling, webhooks or both
Webhooks are fast and cheap, since the other system tells you when something changed. They also get lost. Servers restart, deliveries time out and some vendors stop retrying after a few attempts. For anything that matters we pair webhooks with a scheduled reconciliation job that asks "what changed since my last successful run" and repairs the gaps. Where a vendor offers only polling, we store a cursor or timestamp and respect its rate limits instead of hammering it.
Idempotency, so a retry is harmless
Networks fail halfway. The request reached the other side, the response did not come back, and now you do not know whether the invoice exists. The fix is to make every write safe to repeat. We give each operation a unique key, store the keys we have processed, and look up before creating. Payment providers such as Stripe support idempotency keys for exactly this reason. Retries then run on a queue with growing delays, and after a set number of failures the message moves to a failed list where a person can inspect and replay it.
Where the code should live
If one system is clearly the hub, the integration belongs inside it: a plugin for WordPress API integrations, a local plugin and web services for Moodle integration. When three or more systems are involved, or none of them is yours to extend, a separate middleware application is cleaner. We usually write it as a Laravel API, because queues, scheduling, validation and logging are already there.
Security and life after launch
Credentials sit in environment configuration, never in the repository. Incoming calls are authenticated, validated and rate limited, and we follow OWASP API security practices when you ask us to build and test to them. After launch the integration reports on itself: a log of every exchange, alerts on repeated failures and a simple screen showing what is queued, sent and stuck. Vendors change their APIs, so this is the part that pays for itself.