Webhooks overview

Webhooks push account activity to your systems as it happens, so you do not have to poll. When something happens on one of your accounts (an inbound payment is allocated, an outbound payment leaves, an internal transfer completes), we POST a small signed JSON message to a URL you register with us.

Every delivery is signed with our private key so you can prove it came from us and was not altered. There are no shared secrets, no passwords, and nothing for you to keep in a vault: you verify deliveries with a public key you fetch from us, the same way you already verify tokens.

📘

Webhooks are a notification channel, not the system of record

Payloads are deliberately thin: they identify what happened and give you the id to fetch. Always read the full object from the API before acting on it. If your endpoint is down, nothing is lost: every event is also readable from GET /v1/events for 90 days.


Start here

  1. Build a receiver behind a public HTTPS URL that verifies the signature and answers 2xx in milliseconds.
  2. Send us that URL and the event types you want. We register it and immediately ping it with endpoint.verification.v1.
  3. Answer the ping with any 2xx. The endpoint flips to Active and real events start flowing.
  4. Trigger a sandbox event and check the whole chain: digest, signature, de-duplication, then the fetch from the API.
  5. Schedule a GET /v1/events reconciliation job before you go live.

How it works

  1. Something happens on your account. The platform records the event.
  2. We work out which of your registered endpoints subscribe to that event type.
  3. We POST the event to each of those URLs, signed, with a 15-second timeout.
  4. Your endpoint returns any 2xx. That is the whole contract. The response body is ignored.
  5. If it does not, we retry on a backoff ladder for up to five days.

What a delivery looks like

POST /your/webhook/path HTTP/1.1
Host: webhooks.example.com
Content-Type: application/json
Webhook-Delivery-Id: 4a2f8c61-9b3d-4e77-a0c5-5d1e8f2b7c30
Webhook-Attempt: 1
Webhook-Group-Id: 7c9e5b12-4f68-4a3d-9e21-0b5c6d7e8f90
Content-Digest: sha-256=:rPoQRJ3l9Wiod0Y+++RfM+DU/KrKF19OAVD+7e8F4k4=:
Signature-Input: sig1=("@method" "@authority" "@request-target" "content-digest" "webhook-delivery-id" "webhook-attempt" "webhook-group-id");created=1758100462;keyid="70WOedgzv6hJ63CiLF9TDh6L5XBoL7c71svrGglv65o";alg="rsa-pss-sha256"
Signature: sig1=:<base64 signature>:

{"id":"9f1c2f0e-4c3b-4b8a-9d2a-5f7c1e6b0a11","type":"inbound-payment.received.v1","occurredAt":"2026-09-17T09:14:22.431+00:00","data":{"id":"3b8c5a41-9d27-4e6f-8a10-2c4b7e9f0d55","clientId":"c0a81f42-7b3e-4d19-9f88-1e2d3c4b5a60","accountId":"6d5e4f3a-2b1c-4098-8765-4321fedcba09","amount":{"amountInMinorUnits":125000,"value":1250.00,"currency":"GBP"},"code":"IP-000123"}}

The body is the envelope: an id, a type, an occurredAt and a type-specific data block. Delivery metadata (which delivery this is and which attempt) lives in headers, never in the body, so the body stays byte-identical however many times it is sent. See Webhook events for the envelope and every event type.


Endpoints and subscriptions

A webhook endpoint is one HTTPS URL plus the list of event types it wants.

Endpoints belong to the auth group your API credentials belong to, not to a single client. If your group covers several member clients, one endpoint receives events for all of them and every payload carries a clientId telling you which member it is about. No other group information ever reaches you.

RuleValue
Schemehttps only
HostMust be publicly resolvable. localhost, private, loopback, link-local and carrier-grade NAT addresses are rejected
Credentials in the URLNot allowed (no user:password@)
URL length500 characters
Endpoints per auth group10 (disabled endpoints still occupy a slot)
Duplicate URLs in one groupRejected, case-insensitively
RedirectsNever followed. A 3xx is a failed attempt

Query strings and paths are preserved exactly as you register them; we never canonicalise your URL.


Getting set up

Webhook registration is operated by us today; there is no self-service API yet. To go live:

  1. Send your IFX Payments representative the HTTPS URL, the event types you want, a technical contact and your target go-live date, for each environment separately. Sending all of it at once saves a round trip.
  2. We register the endpoint. It starts in Pending and we immediately send it a verification ping: a normal signed delivery of type endpoint.verification.v1.
  3. Your endpoint answers 2xx. The endpoint flips to Active and real events start flowing. A ping that never succeeds leaves the endpoint Pending, receiving nothing.

Changing the URL later puts the endpoint back to Pending and re-runs verification, so build the new URL before you ask for the change.

📘

Coming later

The webhooks:manage OAuth scope is reserved for self-service endpoint management. It grants nothing today.


Events you can subscribe to

Event typeRaised when
inbound-payment.received.v1An inbound payment has been allocated to one of your accounts
inbound-payment.returned.v1An inbound payment has gone back to the remitter
outbound-payment.scheduled.v1An outbound payment is accepted and waiting for its value date
outbound-payment.awaiting-funds.v1An outbound payment is waiting for the debiting account to be funded
outbound-payment.sent.v1An outbound payment has left for the beneficiary bank
outbound-payment.rejected.v1An outbound payment will not be sent
outbound-payment.returned.v1The beneficiary bank sent an outbound payment back
outbound-payment.cancelled.v1An outbound payment was cancelled before it was sent
internal-payment.completed.v1A transfer between your own accounts has completed
fx-trade.booked.v1A trade is agreed and the rate is fixed
fx-trade.settled.v1Both legs of a trade have settled
fx-trade.failed.v1A trade will not settle
transaction.created.v1A ledger movement was booked on one of your currency accounts

Subscribe only to the types you handle. transaction.created.v1 is the ledger feed and much higher volume than the rest: one payment or trade produces several movements.

Plus one system event you cannot subscribe to and will always receive: endpoint.verification.v1, the ping that proves a new URL works.

Payloads and examples for every type are in Webhook events.


If you would rather pull

GET /v1/events returns the same events over HTTP, newest first, for 90 days after they were raised. It needs the events:read scope. Use it as:

  • the recovery path when your endpoint was down or disabled, and
  • a complete integration route on its own if you would rather not run a receiver at all.

The id is the same value the webhook body carries, so the two de-duplicate against each other. See Building a receiver for the polling pattern.


Environments

Sandbox and production are separate in every respect: separate endpoint registrations, separate subscriptions, and separate signing keys. A delivery signed in sandbox will not verify against production's key set, which is deliberate. Point each environment's receiver at that environment's key set URL.


What we guarantee, and what we do not

DeliveryAt least once. Retries and replays mean the same id can arrive more than once, so de-duplicate on it
OrderingNot guaranteed. Reconcile with occurredAt and a fetch from the API
SuccessAny 2xx within 15 seconds
RetriesBackoff ladder of 30s, 5m, 30m, 2h, 5h, then every 8 hours, until the event is five days old
ThroughputOne delivery at a time plus the 15-second timeout is a hard ceiling. A handler that answers in 200ms sustains hundreds of events a minute; one that takes 10 seconds sustains six
ConcurrencyOne delivery in flight per endpoint at a time
PayloadThin and stable for a given type name; new optional fields may appear without notice
BodyByte-identical across every attempt and replay of the same event

Next


Did this page help you?